Android View的主要工作流程包括:测量measure-->布局layout-->绘制draw,本篇主要讲解measure的过程。
在布局文件中,每个View都具有layout_width和layout_height两个属性,用于指定View的尺寸。这两个属性的取值只有三种:固定数值、wrap_content、match_parent。在measure的过程中,父容器会根据自己的限制信息和子View的尺寸属性算出子View的限制信息并传递给子View,子View再结合这些信息和自己的内容计算最终的尺寸。
这个限制信息就是MeasureSpec类。它实际上相当于一个32位的int,其中高两位代表父布局对子View的限制模式mode,剩下30位代表父布局为子View指定的尺寸size 。
mode有三种,分别是:
1、EXACTLY精确的,即子View不用管自己的内容,直接使用MeasureSpec中给的size。
2、AT_MOST最多的,即子View根据自己的内容计算一个合适的尺寸,但不能超过MeasureSpec中给的size。
3、UNSPECIFIED不限定,即子View想要多大就是多大,不用管MeasureSpec中给的size。
整个测量过程是从根节点的measure()
方法开始的,虽然根节点是一个ViewGroup,但由于ViewGroup继承自View,而View中的measure()
方法是final
类型,不能被子类重写,因此还是执行的View中的measure()
方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec)
...
}
measure()
方法中会调用onMeasure()
方法,onMeasure()
可以被子类重写。一般来说,ViewGroup会在onMeasure()
方法中对子View进行测量,然后根据父布局给出的限制结合子View的尺寸以及自己的布局规则得到自己的最终尺寸,并调用setMeasuredDimension()
进行设置。
1、measureChildren()
遍历所有子View,跳过状态为Gone的,依次调用measureChild()
进行测量
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
2、measureChild()
根据自己的MeasureSpec和Padding以及子View指定的尺寸,调用getChildMeasureSpec()
方法计算出子View的MeasureSpec,最后调用child.measure()
将子View的MeasureSpec传给子View,由子View自己去计算自己的尺寸
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
3、measureChildWithMargins()
该方法与measureChild()
类似,只不过计算时考虑了子View的margin
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);
}
4、getChildMeasureSpec()
根据父布局自己的MeasureSpec和子View的尺寸属性计算出子View的MeasureSpec
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:
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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
上述代码简单的说就是:
(1)、如果子View的尺寸属性为具体数值,则子View的specMode为EXACTLY,specSize就用该数值。即子View指定了具体的数值,就精确的用指定的数值。
(2)、否则
size = 父布局的specSize - 父布局的padding
①、如果父布局的specMode为UNSPECIFIED未限定,那么子View的specMode也为UNSPECIFIED,specSize要么为0要么为size反正会被忽略。
②、如果父布局的specMode为EXACTLY精确的,并且子View的尺寸属性为match_parent,则子View的specMode为EXACTLY,specSize为size。即精确的与父布局同样大小。
③、其他情况,则子View的specMode为AT_MOST,specSize为size。即最多不超过父布局的大小。
5、计算了子View的MeasureSpec之后再调用child.measure(childWidthMeasureSpec, childHeightMeasureSpec)
方法计算子View的真实尺寸,即回到了开头所说的View中的measure()
方法,该方法中再调用子View的onMeasure()
方法。如果子View又是一个ViewGroup,则重复上述过程,否则如果子View是一个View,那么onMeasure方法的实现逻辑为:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
int viewSize = 0;
swith (mode) {
case MeasureSpec.EXACTLY:
viewSize = size; //当前View尺寸设置为父布局尺寸
break;
case MeasureSpec.AT_MOST:
viewSize = Math.min(size, getContentSize()); //当前View尺寸为内容尺寸和父布局尺寸当中的最小值
break;
case MeasureSpec.UNSPECIFIED:
viewSize = getContentSize(); //内容有多大,就设置尺寸为多大
break;
default:
break;
}
setMeasuredDimension(viewSize);
}
6、计算了子View的尺寸后,再根据父布局给出的限制结合子View的尺寸以及ViewGroup自身的布局规则得到最终尺寸
7、调用setMeasuredDimension(int measuredWidth, int measuredHeight)
方法设置ViewGroup自身的最终尺寸