1、ViewRootImpl.performTraversals方法
ViewRootImpl是一个Activity UI显示的一个控制类,在View和WindowManager之间起一个中间管理的角色。该类的performTraversals方法里面会相继调用performMeasure、performLayout、performDraw三个方法,继而调用根布局DecorView的mersure、layout、draw方法,DecorView又会调用其孩子的mersure、layout、draw方法,进而完成一个Activity的绘制。本文只关注measure方面的流程,我们先看一下performMeasure调用的地方
private void performTraversals() {
......
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " coveredInsetsChanged=" + contentInsetsChanged);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
}
我们看到传给performMeasure的两个参数是childWidthMeasureSpec和childHeightMeasureSpec,这是两个int类型的的变量。childWidthMeasureSpec里面又包含了两个部分mode和size,一个int变量是32位的,最高两位表示mode,其余30位用来存放size,childHeightMeasureSpec同样,这两个变量分别存放了宽和高的信息。我们看下getRootMeasureSpec方法,看看这两个变量是如何生成的。我们以宽为例,给getRootMeasureSpec方法传的参数值mWidth和lp.width,mWidth可以理解为Window的宽度,lp是WindowManager.LayoutParams类的实例,其在声明的时候已经初始化了
final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
public LayoutParams(int width, int height) {
this.width = width;
this.height = height;
}
可知,lp.width默认值就是LayoutParams.MATCH_PARENT,lp.height的默认值以为LayoutParams.MATCH_PARENT。
2、ViewRootImpl.getRootMeasureSpec方法
先直接看源码:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
如果rootDimension等于ViewGroup.LayoutParams.MATCH_PARENT,即-1,那么size为windowSize,即屏幕的宽或者高,mode的值为MeasureSpec.EXACTLY。如果rootDimension等于ViewGroup.LayoutParams.WRAP_CONTENT,即-2,那么size为windowSize,即屏幕的宽或者高,mode的值为MeasureSpec.AT_MOST。否则,rootDimension为一个具体的尺寸值,那么size为rootDimension,mode的值为MeasureSpec.EXACTLY。在第1节中,我们知道该方法传进来的参数windowSize为窗口的宽和高,rootDimension的值为ViewGroup.LayoutParams.MATCH_PARENT,所以该方法返回值所包含的size就是窗口大小,mode值为MeasureSpec.EXACTLY。
3、ViewRootImpl.performMeasure方法
看下源码:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
可见,里面就是调用了mView.measure方法,而mView就是Activity的根布局DecorView,而DecorView是FrameLayout的子类,并且没有重写measure方法,所以最终调用的还是View.measure方法,参数childWidthMeasureSpec和childHeightMeasureSpec的含义在第2节中说过,传给DecorView的宽高信息size部分就是window的宽和高,mode就是MeasureSpec.EXACTLY,DecorView给其孩子view传的时候mode值就具体问题具体分析了,不固定。
4、View.measure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
为了突出重点,其他代码都省略了,我们只需关注view的measure方法里面调用了onMeasure方法。
5、View.onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
这里出来一个新的mode值MeasureSpec.UNSPECIFIED,这里先说明一下,这个值其实没有用到,无需关注太多,至于为什么说没有用到,首先ViewRootImpl给DecorView传的时候其实就是只传了个MeasureSpec.EXACTLY,而DecorView给其孩子传的时候没有用到MeasureSpec.UNSPECIFIED下文会讲。getDefaultSize方法就是,如果mode是MeasureSpec.EXACTLY或者MeasureSpec.AT_MOST,那么自己的宽和高就会设置成parent传进来的宽和高,看到这里,我们可以推想,view的宽和高岂不总是和parent宽和高相同了,这个实际开发遇到的不一样啊,子view的宽高绝大多数都比parent的小啊,其实这也不奇怪,因为我们遇到的容器类如FrameLayout或者非容器类比如TextView都重写了onMeasure方法。目前只是跟到了DecorView,其就重写了onMeasure方法。其实容器类的onMeasure方法最终都会调用measureChild或者measureChildWithMargins方法来对其孩子进行测量,我们看一下measureChildWithMargins的方法。
6、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
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
该方法最后一句代码可知,容器类的measure方法最终会调用子view的measure方法,我们来看一下给子view的参数是如何产生的,看getChildMeasureSpec方法,以宽为例,看一下getChildMeasureSpec的三个参数的含义,第一个参数parentWidthMeasureSpec,就是当前view的从其parent接收到的宽测量值,第二个参数是mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed,widthUser一般为0,写过布局的都知道,这个应该就是计算子view距parent左右两边的距离和,第三个参数lp.width就跟view的具体配置有关了,可能值为三个ViewGroup.LayoutParams.MATCH_PARENT、ViewGroup.LayoutParams.WRAP_PARENT和一个具体的尺寸值(大于等于0)。下面来看一下getChildMeasureSpec内部实现。
7、ViewGroup.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) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
......
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
为了方便理解上面代码,我画了个图如下:
容器类measure的大致流程就先循环调用子View的measure,当然子View也可以是容器类,这样层层向下调用,所有的子View测量完了,然后容器类再对自己进行测量。另外,第5节说过MeasureSpec.UNSPECIFIED这个值没有用,这里接着解释一下,首先从DecorView传的时候就没有MeasureSpec.UNSPECIFIED,从上图可知针对parent的MeasureSpec.EXACTLY和MeasureSpec.AT_MOST,给子view传的时候,也不会传MeasureSpec.UNSPECIFIED,所以说该值在measure的过程中可以认为没有用到。
以上是本人针对measure流程做的一些梳理,如有问题欢迎指出。
AT_MOST
由上图可知