一、ViewRoot和DecorView
ViewRoot对应于ViewRootImpl类,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绘制出来。
measure过程:决定了View的测量宽/高,measure完成以后,可以通过getMeasuredWidth和getMeasuredHeight方法来获取View测量后的宽/高,在几乎所有的情况下它都等同于View最终的宽/高,但是特殊情况除外。
layout过程:决定了View的四个顶点的坐标和View的最终宽/高,完成以后,可以通过getTop、getBottom、getLeft和getRight来拿到View的四个顶点的位置,并可以通过getWidth和getHeight方法来拿到View的最终宽/高。
draw过程:决定了View的显示,只有draw方法完成以后View的内容才能呈现在屏幕上。
二、MeasureSpec
在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个MeasureSpec来测量出View的宽/高。
MeasureSpec代表一个32位int值,高2位代表SpecMode,即测量模式;低30位代表SpecSize,即在某种测量模式下的规格大小。
SpecMode有三类:
UNSPECIFIED:父容器不对View有任何限制,要多大给多大。
EXACTLY:父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和固定大小这两种模式。
AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content。
三、MeasureSpec和LayoutParams的对应关系
View的LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽/高。
对于顶级View(即DecorView),其MeasureSpec由窗口的尺寸和自身的LayoutParams来共同确定;
对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同确定;
MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高。
1.DecorView的MeasureSpec
ViewRootImpl#measureHierarchy:
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); //desiredWindowWidth为屏幕宽度尺寸
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); //desiredWindowHeight为屏幕高度尺寸
perforMeasure(childWidthMeasureSpec , childHeightMeasureSpec );
ViewRootImpl#getRootMeasureSpec:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch(rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
DecorView的MeasureSpec的创建过程,根据LayoutParams中的宽/高的参数来划分:
1.LayoutParams.MATCH_PARENT:精确模式;大小为窗口的大小。
2.ViewGroup.LayoutParams.WRAP_CONTENT:最大模式;大不不定,但是不能超过窗口的大小。
3.固定大小:精确模式;大小为LayoutParams中指定的大小。
2.普通View的MeasureSpec
ViewGroup#measureChildWithMargins:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams)child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUserd, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUserd, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec );
}
ViewGroup#getChildMeasureSpec:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec); //父容器的SpecMode
int specSize = MeasureSpec.getSize(spec); //父容器的SpecSize
int size = Math.max(0, specSize - padding); //注意:size表示父容器的剩余空间大小,等于父容器的SpecSize减去父容器中已占用的空间大小
int resultSize = 0; //当前View的SpecSize
int resultMode = 0; //当前View的SpecMode
switch(specMode) { //父容器的SpecMode
case MeasureSpec.EXACTLY: //父容器的SpecMode为精确模式
if(childDimension >= 0) { //当前View的LayoutParams为固定大小
resultSize = childDimension; //当前View的SpecSize为固定大小
resultMode = MeasureSpec.EXACTLY; //当前View的SpecMode为精确模式
} else if(childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if(childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.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;
case MeasureSpec.UNSPECIFIED:
if(childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if(childDimension == LayoutParams.MATCH_PARENT) {
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if(childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
普通View的MeasureSpec的创建过程,根据父容器的MeasureSpec、子元素的LayoutParams、父容器的剩余空间大小来确定。
其中,父容器的剩余空间大小等于父容器的SpecSize减去父容器中已占用的空间大小,而
父容器中已占用的空间大小由父容器的padding、子元素的margin以及已使用的空间大小三部分组成。
规则如下:
注意:parentSize是指父容器的剩余空间大小
- 父容器的SpecMode为EXACTLY
1.1 子元素的LayoutParams为固定大小
子元素的SpecMode为EXACTLY,SpecSize为固定大小。
1.2 子元素的LayoutParams为MATCH_PARENT
子元素的SpecMode为EXACTLY,SpecSize为parentSize。
1.3 子元素的LayoutParams为WRAP_CONTENT
子元素的SpecMode为AT_MOST,SpecSize为parentSize。 - 父容器的SpecMode为AT_MOST
2.1 子元素的LayoutParams为固定大小
子元素的SpecMode为EXACTLY,SpecSize为固定大小。
2.2 子元素的LayoutParams为MATCH_PARENT
子元素的SpecMode为AT_MOST,SpecSize为parentSize。
2.3 子元素的LayoutParams为WRAP_CONTENT
子元素的SpecMode为AT_MOST,SpecSize为parentSize。 - 父容器的SpecMode为UNSPECIFIED
3.1 子元素的LayoutParams为固定大小
子元素的SpecMode为EXACTLY,SpecSize为固定大小。
3.2 子元素的LayoutParams为MATCH_PARENT
子元素的SpecMode为UNSPECIFIED,SpecSize为0。
3.3 子元素的LayoutParams为WRAP_CONTENT
子元素的SpecMode为UNSPECIFIED,SpecSize为0。