Android UI绘制流程。我们平时写布局应该对ViewGroup非常了解。ViewGroup相当于一个容器,里边可以放ViewGroup或者View对象。而ViewGroup和View的包含区别就是ViewGroup里边可以添加,View不可以在添加。
了解了ViewGroup和View的关系后,那么ViewGroup里的ViewGroup和View是经过怎样的过程最终显示到设定的位置的呢?其实ViewGroup就相当于一棵树,View相当于树的叶子。ViewGroup要经历 测量onMeasure(),摆放onLayout(),画 onDraw()三个主要方法最终显示出来。
首先来看onMeasure()测量的方法。
首先我们提出一个猜想,在整个的ViewGroup的测量步骤中,首先要测量并确定所有的子View的大小,再确定View树的根ViewGroup的大小,就像数据结构中的树的前序遍历。
根据前面的猜想,我们应该先测量所有的子View。在ViewGroup方法的中,我们看到了measureChildren()方法,所以可以根据这个方法作为线索,向下分析。
1 measureChildren() 测量所有子View
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; //1 遍历子View for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { //2 测量子View measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
这个方法有两个参数,widthMeasureSpec和heightMeasureSpec。所以在看这个方法之前,先来看一下MeasureSpec。
MeasureSpeck用32位的int值来表示,高两位表示MeasureSpec的SpecMode属性,低30位表示SpecSize属性。
SpecMode有三种:
SpecSize就表示控件的大小的值。
SpecMode和SpecSize可以通过MeasureSpec的getSpecMode()和getSpecSize()方法来获取。
同时,也可以把SpecMode和SpecSize通过MeasureSpec的makeMeasureSpec()方法来合成一个MeasureSpec。
下面再来看上面这段代码,从这个方法中可以看出,首先要遍历所有的子View,再调用measureChild()方法测量子View。下面来看测量View自己的方法。
2 measureChild() 子View测量自己
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { //1 获取child自己的相关参数 final LayoutParams lp = child.getLayoutParams(); //2 获取childWidthMeasureSpec 和 childHeightMeasureSpec final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); //3 根据宽高属性,测量自己 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
1 这个方法中首先获取了child自己的参数Layoutparams。
2 然后根据所在ViewGroup的MeasureSpec和padding,本身的宽度来获取宽和高的MeasureSpec。
3 根据宽和高的MeasureSpec来测量自己。
下面来看ViewGroup中测量的核心:
2.1 getChildMeasureSpec() 获得子View的MeasureSpec
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { //1 获得父ViewGroup的specMode和specSize int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; //2判断父ViewGroup的specMode switch (specMode) { //2.1如果父ViewGroup是一个具体的值 case MeasureSpec.EXACTLY: //2.1.1 child是一个具体值 if (childDimension >= 0) { //child的size将会是child自己设定的值 resultSize = childDimension; //因为child的size是精确值,所以child的specMode是EXACTLY resultMode = MeasureSpec.EXACTLY; //2.1.2 child想和父ViewGroup一样大 } else if (childDimension == LayoutParams.MATCH_PARENT) { //因为此时父ViewGroup的size是确定的,child又想和父ViewGroup一样大,所以child的size和父ViewGroup的size相等 resultSize = size; //因为child的size是精确值,所以child的specMode是EXACTLY resultMode = MeasureSpec.EXACTLY; //2.3.3 child想要自己本身的大小 } else if (childDimension == LayoutParams.WRAP_CONTENT) { //限制child不能比父ViewGroup大 让child的size和父ViewGroup的size相等 resultSize = size; //child大小此时不能确定 所以specMode也是AT_MOST resultMode = MeasureSpec.AT_MOST; } break; //2.2如果父ViewGroup是一个最大值 case MeasureSpec.AT_MOST: //2.2.1 child是一个具体值 if (childDimension >= 0) { //child的size将会是child自己设定的值 resultSize = childDimension; //因为child的size是精确值,所以child的specMode是EXACTLY resultMode = MeasureSpec.EXACTLY; //2.3.2 child想和父ViewGroup一样大 } else if (childDimension == LayoutParams.MATCH_PARENT) { //child想和父ViewGroup一样大,但是父ViewGroup的size是不固定的,所以限制child不能比父ViewGroup大 resultSize = size; // child的specMode也是AT_MOST resultMode = MeasureSpec.AT_MOST; //2.3.3 child想要自己本身的大小 } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. //同样限制child不能比父ViewGroup大 resultSize = size; //同样child的specMode也是AT_MOST resultMode = MeasureSpec.AT_MOST; } break; //2.3如果父ViewGroup想知道子控件有多大 case MeasureSpec.UNSPECIFIED: //2.3.1 child是一个具体值 if (childDimension >= 0) { //child的size将会是child自己设定的值 resultSize = childDimension; //因为child的size是精确值,所以child的specMode是EXACTLY resultMode = MeasureSpec.EXACTLY; //2.3.2 child想和父ViewGroup一样大 } else if (childDimension == LayoutParams.MATCH_PARENT) { //child的size将会是0或者父ViewGroup的大小 resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; //因为child的父ViewGroup是未指定的,child又想和父ViewGroup一样大,所以child的specMode也是UNSPECIFIED resultMode = MeasureSpec.UNSPECIFIED; //2.3.3 child想要自己本身的大小 } else if (childDimension == LayoutParams.WRAP_CONTENT) { //child的size将会是0或者父ViewGroup的大小 resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; // 同时child的specMode也是UNSPECIFIED resultMode = MeasureSpec.UNSPECIFIED; } break; } //3 把child的SpecSize和SpecMode合成一个MeasureSpec并返回 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
这段代码可以算是ViewGroup中的重点代码了。我几乎把所有的代码都写了注释。这个方法主要是结合ViewGroup的MeasureSpec和child本身的大小来确定child的MeasureSpec。具体是如何影响child的MeasureSpec的,可以看我写的注释。写的非常清楚,这里不再赘述。
获得了child的MeasureSpec后,开始调用
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
而measure()方法中的测量工作主要是在onMeasure()中来完成了的。来看下面这段伪代码:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { onMeasure(widthMeasureSpec, heightMeasureSpec); }
再来看onMeasure()方法:
3 onMeasure()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
在onMeasure()方法中,
1 首先获得推荐的最小宽度或高度。
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }
protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }
2 根据推荐的高度和宽度,在结合child的MeasureSpec。来确定child的默认宽高。
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; }
3最后通过setMeasuredDimension()方法来把存储View的宽和高
4 setMeasuredDimension
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); }
存储真正的操作是在setMeasuredDimensionRaw()方法中,在setMeasuredDimensionRaw()方法中直接把宽和高保存在成员变量中。
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }
至此,View的宽和高已经确定。测量过成完成。但是这只是ViewGroup其中的一个子View的过程。如果ViewGroup1的子View也是一个ViewGroup2。那么最先测量的应该是ViewGroup2的子View。这是个递归的过程,就像是树的遍历。先遍历最小最深的结点。
需要注意的是,如果ViewGroup在测量过程中,子View也是一个ViewGroup。那么是怎么实现递归的呢?如FramLayout是怎么再去测量呢?其实FrameLayout重写了onMeasure()方法,在此方法中,还是要先去遍历所有的子View,然后再测量。如下:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear(); int maxHeight = 0; int maxWidth = 0; int childState = 0; //1 遍历每个child for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { //2 开始测量每个child measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } }
这是FramLayout中的onMeasure()方法的一部分,可以看出先去遍历,然后再去测量每个childView。