一个View出现在屏幕要经过三个过程:measure(测量)、layout(布局)、draw(绘制)。这三个过程均是通过ViewRoot来完成,ViewRoot对应ViewRootImpl类,它是连接WindowManager和DecorView(顶级View)的纽带。当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。
measure过程决定了View的宽/高,Measure完成以后,可以通过getMeasuredWidth和getMeasuredHeight方法来获取到View测量后的宽/高,在几乎所有情况下它都等于View最终的宽/高,但特殊情况除外。
layout过程决定了View的四个顶点的坐标和实际的View的宽/高,完成以后,可通过getTop、getBottom、getLeft和getRight来拿到View的四个顶点的位置,并可通过getWidth和getHeight方法来拿到View的最终宽/高。
draw过程决定了View的显示。
DecorView就是顶级View或叫根View,一般情况下是一个竖直方向的LinearLayout,分Title和Content两部分(具体看Android版本及主题),我们在Activity的onCreate方法中调用的setContentView就是设置它的Content内容。Content这块的id是content,所以,如果想获得它的对象,可以这样:
ViewGroup contentView = (ViewGroup)findViewById(android.R.id.content);
然而我们通过setContentView设置的View,可以这样获得:contentView.getChildAt(0);。它们几个的关系,可以参考下图:
ViewRootImpl类中有方法performTraversals,performTraversals会依次调用performMeasure、performLayout和performDraw三个方法,它们分别完成DecorView的measure、layout 和 draw三大流程。从源码可以看到:perormMeasure方法 调用了 measure方法,measure 方法又调用onMeasure 方法。在onMeasure方法中则会对所有子元素进行measure过程,这时measure流程就从父容器传递到子元素中,这样就完成一次measure过程。接着子元素会重复父容器的过程。同理performLayout和performDraw类似。可以参考下图:
一般情况下,我们在做界面布局时LayoutParams会有三种参数,分别是:match_parent、wrap_content 和 数值大小(例如:10dp)。其实这三种参数对应了两种模式,这种模式叫SpecMode(测量模式)。要了解SpecMode是什么就要先来说说MeasureSpec。MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize(测量模式下的规格大小)。MeasureSpec至所以通过SpecMode和SpecSize打包成一个int值,想必是为了避免过多的对象内存分配。
View的尺寸规格是受父容器影响和MeasureSpec的决定。在measure过程中,系统会将View的Layoutparams根据父容器所施加的规则转换成对应的MeasureSpec值,此值就是用于测量出View的宽和高。注意测量的宽和高不一定等于最终宽和高。
说回刚才的三种参数对应两种SpecMode,其实SpecMode共有三类,每一类表示的含义如下所示:
UNSPECIFIED 父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态。
EXACTLY 父容器已检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应LayoutParams中的match_parent和具体的数值这两种模式。
AT_MOST 父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content
DecorView(顶级View)其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定。可见在ViewRootImpl中的measureHierarchy方法中有如下一段代码展示了DecorView的MeasureSpec的创建过程,其中desiredWindowWidth和desiredWindowHeight是屏幕的尺寸:
private boolean measureHierarchy(View host,LayoutParams lp, Resources res, int desiredWindowWidth, intdesiredWindowHeight) {
……
childWidthMeasureSpec= getRootMeasureSpec (desiredWindowWidth, lp.width);
childHeightMeasureSpec= getRootMeasureSpec (desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
……
}
getRootMeasureSpec方法的实现:
private static int getRootMeasureSpec(int windowSize,int rootDimension) {
intmeasureSpec;
switch(rootDimension) {
caseViewGroup.LayoutParams.MATCH_PARENT:
// 精确模式,大小就是窗口的大小。
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
caseViewGroup.LayoutParams.WRAP_CONTENT:
// 最大模式,大小不定,但是不能超过窗口的大小。
measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST);
break;
default:
// 精确模式,大小为LayoutParams中指定的大小。
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY);
break;
}
returnmeasureSpec;
}
View的measure过程由ViewGroup传递而来,先看一下ViewGroup的measureChildWithMargins(在实现类的onMeasure中调用,如:LinearLayout->measureVertical-> measureChildBeforeLayout-> measureChildWithMargins)方法:
protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, intheightUsed) {
finalMarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final intchildWidthMeasureSpec = getChildMeasureSpec (parentWidthMeasureSpec,mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed,lp.width);
final intchildHeightMeasureSpec = getChildMeasureSpec (parentHeightMeasureSpec,mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed,lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
在调用子元素的measure方法之前会先通过getChildMeasureSpec方法来得到子元素的MeaureSpec。所以说,子元素的MeasureSpec的创建与父容器的MeasureSpec和子元素本身的LayoutParams有关,此外还和View的margin及padding有关。我们来看看getChildMeasureSpec方法的实现:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode= MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch(specMode) {
// 父元素是精确模式,大小就是窗口的大小(MATCH_PARENT或指定大小)。
case MeasureSpec.EXACTLY:
if(childDimension >= 0) {
//子元素是它自己设定的数值大小
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 子元素要成为跟父元素一样的尺寸
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 子元素要根据内容认定自己的大小,但不能比父元素大
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父元素是最大模式,大小不定,但是不能超过窗口的大小(WRAP_CONTENT)。
caseMeasureSpec.AT_MOST:
if(childDimension >= 0) {
// 子元素是它自己设定的数值大小
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 子元素要成为跟父元素一样的尺寸,但父元素不是固定的,而且子元素不会比父元素大
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 子元素要根据内容认定自己的大小,但不能比父元素大
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parentasked to see how big we want to be(这模式主要用于系统内部多次Measure情形,一般来说,我们不需要关注此模式)
caseMeasureSpec.UNSPECIFIED:
if(childDimension >= 0) {
//Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//Child wants to be our size... find out how big it should
//be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//Child wants to determine its own size.... find out how
//big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
returnMeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
——本博文部分内容参考自《Android开发艺术探索》