View树的绘制流程是从ViewRoot的performTraversals()开始的,树的遍历是有序的,由父视图到子视图,每一个 ViewGroup 负责绘制它所有的子视图,而最底层的 View 会负责测绘自身。ViewRootImpl是ViewRoot的实现类,在ViewRootImpl的performTraversals中顺序调用了performMeasure()、performLayout()、performDraw()三个方法,分别对应测量、布局、绘制三个过程。
1、测量
ViewRootImpl的performMeasure()调用了View的measure()方法,此方法为 final 类型,不可被复写,但 measure 调用链最终会回调 View/ViewGroup 对象的 onMeasure()方法。
如果ViewGroup中包含多个子View,则每个子View都要测量一次,ViewGroup中的measureChildren()负责测量子视图的大小:
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);
}
}
}
measureChildren()遍历子视图后,调用了measureChild()方法测量每一个子视图,measureChild()方法又调用了View的measure()方法。
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);
}
View的measure()方法接收两个参数,childWidthMeasureSpec和childHeightMeasureSpec,这两个值分别用于确定视图宽高的规格和大小。
MeasureSpecs 是测量的规格,包含测量要求和尺寸的信息,其值由specSize和specMode共同组成的,有三种模式:
UNSPECIFIED
父视图不对子视图有任何约束,它可以达到所期望的任意尺寸。比如 ListView、ScrollView,一般自定义 View 中用不到,
EXACTLY
父视图为子视图指定一个确切的尺寸,而且无论子视图期望多大,它都必须在该指定大小的边界内,对应的属性为 match_parent 或具体值,比如 100dp,父控件可以通过MeasureSpec.getSize(measureSpec)直接得到子控件的尺寸。
AT_MOST
父视图为子视图指定一个最大尺寸。子视图必须确保它自己所有子视图可以适应在该尺寸范围内,对应的属性为 wrap_content,这种模式下,父控件无法确定子 View 的尺寸,只能由子控件自己根据需求去计算自己的尺寸,这种模式就是我们自定义视图需要实现测量逻辑的情况。
onMeasure()才是测量View大小的地方,会调用getDefaultSize()来获取View的大小:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
getDefaultSize()根据MeasureSpec获取视图的宽高:
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;
}
然后把得到的View宽高设置给setMeasuredDimension(),setMeasuredDimension()调用了setMeasuredDimensionRaw(),把宽高赋值给mMeasuredWidth、mMeasuredHeight;
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
至此,View的测量过程结束,此时调用View的getMeasuredWidth()、getMeasuredHeight()可以获得测量的宽高。