View的三大流程之View的测量
扫描二维码
随时随地手机看文章
1、public class
View
extends Object
implements Drawable.Callback
KeyEvent.Callback AccessibilityEventSource
java.lang.Object
↳
android.view.View
Class Overview
This class represents the basic building block for user interface components. A View occupies a rectangular area on the screen and is responsible for drawing and event handling. View is the base class forwidgets, which are used to create interactive UI components (buttons, text fields, etc.).
public abstract class
View是Android中所有控件的基类,Button/TextView/RelativeLayout/ListView等,其共同基类都是View。View是界面层的控件的一种抽象,待变一个控件。
ViewGroup
extends View
implements ViewManager
ViewParent
java.lang.Object ↳ android.view.View ↳ android.view.ViewGroup Class Overview
A ViewGroup
is a special view that can contain other views (called children.) The view group is the base class for layouts and views containers.
ViewGroup内部包含一组View,ViewGroup也继承自View,这就说明View本身就可以使单个控件或多个控件组成的一组控件。
UI界面架构图:
每个Activity都包含一个Window对象,Window对象通常由PhoneWindow来实现。PhoneWindow将一个DecorWindow设置为整个窗口的根View。DecView里有所有View的监听事件,通过WindowManagerService进行接收,并通过Activity对象回调相应的onClickListener。显示界面上,将屏幕分为两部分,分别为TitleView和ContentView。ContentView是一个ID为content的FrameLayout,布局文件activity_main.xml就是设置在这样一个FrameLayout中。
2、View的位置参数:
View根据上图得到View的宽高和坐标(相对于ViewGroup,而不是原点)的关系:
width = right - left;
height = bottom - top;
获取这几个参数的方法如下:
mLeft = getLeft(); mRight = getRight(); mTop = getTop(); mBottom = getBottpm();
Android3.0开始View增加了几个参数:x,y,translationX,translationY,其中x,y是View左上角的坐标,translationX,translationY是View左上角相对于父容器的偏移量:
x = left + translationX;
y = top + translationY;
需要注意:View在平移过程中,top/left表示的是原始左上角的位置信息,其值在移动期间不会发生改变,变化的是x,y,translationX,translationY.
3、View的三大流程(measure,layout,draw)之View的测量
View的绘制流程从ViewRoot的performTraversals方法开始,ViewRoot对应于ViewRootImpl类,是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象与DecorView相关联:
root = new ViewRootImpl(view.getContext(),display); root.setView(view,wparams,panelParentView);
View的绘制流程从ViewRoot的performTraversals方法开始,经过measure,layout,draw三个过程将一个View绘制出来。
performTraversals会依次调用performMeasure,performLayout,performDraw三个方法,分别完成顶级View的measure,layout,draw三大流程,其中performMeasure中会调用measure方法,在measure方法中又调用onMeasure方法,在inMeasure方法中会对所有子元素进行measure过程,此时measure流程就从父元素传到子元素了,这样完成了一次measure过程。接着子元素会重复父容器的measure过程,如此反复完成整个View树的遍历。performLayout与performDraw与之类似,不同的是performDraw的传递过程是在draw方法中通过dispatchDraw来实现。
测量过程在onMeasure()方法中进行。
Android提供了一个功能强大的类--MeasureSpec类。
public static class
View.MeasureSpec
extends Object
java.lang.Object
↳
android.view.View.MeasureSpec
Summary:
Constants |Ctors |
Methods | Inherited Methods |
[Expand All]
Added in API level 1
public static class
View.MeasureSpec
extends Object
java.lang.Object
↳
android.view.View.MeasureSpec
Class Overview
A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode. There are three possible modes:
UNSPECIFIED The parent has not imposed any constraint on the child. It can be whatever size it wants.EXACTLY The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.AT_MOST The child can be as large as it wants up to the specified size.
MeasureSpec是一个32位的int值,高2位为测量模式(SpecMode),低30位是测量大小(SpecSize).
View默认的onMeasure()方法只支持EXACTLY模式,因此如果让自定义View支持wrap_content属性,就必须重写onMeasure()方法来指定wrap_content大小(进一步说明,由源码得出,wrap_content下的SpecMode是AT_MOST,此时的specSize是parentSize,即如果不指定wrap_content,在布局中使用wrap_content就相当于使用match_parent).
解决方法:
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){ super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){ setMeasureDimension(mWidth,mHeight); }else if(widthSpecMode == MeasureSpec.AT_MOST){ setMeasureDimension(mWidth,heightSpecSize); }else if(heightSpecMode == MeasureSpec.AT_MOST){ setMeasureDimension(widthSoecSize,mHeight) } }
在上面代码中指定View的默认宽高(mWidth/mHeight),并在wrap_content时设置此宽高。
Demo:
MeasureView.java
package sunny.example.ahthreeviewmeasure; import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.widget.Button; import android.widget.TextView; public class MeasureView extends View{ public MeasureView(Context context) { super(context); // TODO Auto-generated constructor stub } public MeasureView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } public MeasureView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub } @Override protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){ super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){ setMeasuredDimension(400,400);//指定宽高 }else if(widthSpecMode == MeasureSpec.AT_MOST){ setMeasuredDimension(400,heightSpecSize); }else if(heightSpecMode == MeasureSpec.AT_MOST){ setMeasuredDimension(widthSpecSize,400); } } }
activity_main.xml
MainActivity.java
package sunny.example.ahthreeviewmeasure; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; public class MainActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //MeasureView mView = (MeasureView)findViewById(R.id.measureView); } }