Android应用层View绘制流程之measure,layout,draw三步曲

一箫一剑平生意,负尽狂名十五年。这篇文章主要讲述Android应用层View绘制流程之measure,layout,draw三步曲相关的知识,希望能为你提供帮助。
概述上一篇博文对DecorView和ViewRootImpl的关系进行了剖析,这篇文章主要是来剖析View绘制的三个基本流程:measure,layout,draw,只有把这三个基本流程搞清楚了,平时在自定义View的时候才会有清晰的思路!开始进入正题。
View的measure过程三个流程均是从ViewRootImpl的performTraversals方法开始的,如下所示:

private void performTraversals() { ...... int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ...... mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); ...... mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); ...... mView.draw(canvas); ...... }

首先看下getRootMeasureSpec方法,如下所示:
/** * Figures out the measure spec for the root view in a window based on it‘s * layout params. * * @param windowSize *The available width or height of the window * * @param rootDimension *The layout params for one dimension (width or height) of the *window. * * @return The measure spec to use to measure the root view. */ 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; }

从上面的注释可以看出这个getRootMeasureSpec是为了根据根视图的LayoutParams计算根视图的MeasureSpec,这个根视图就是上篇博客讲的DecorView。
关于MeasureSpec关于MeasureSpec来做一个简单的说明:通过MeasureSpec.makeMeasureSpec来得到一个32位的整数,高两位代码测量模式mode,低30位代表测量大小size,如下所示:
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 < < MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } }

然后再通过getMode和getSize这两个方法来得到对应的测试模式mode和测量尺寸size,如下所示:
/** * Extracts the mode from the supplied measure specification. * * @param measureSpec the measure specification to extract the mode from * @return {@link android.view.View.MeasureSpec#UNSPECIFIED}, *{@link android.view.View.MeasureSpec#AT_MOST} or *{@link android.view.View.MeasureSpec#EXACTLY} */ @MeasureSpecMode public static int getMode(int measureSpec) { //noinspection ResourceType return (measureSpec & MODE_MASK); }/** * Extracts the size from the supplied measure specification. * * @param measureSpec the measure specification to extract the size from * @return the size in pixels defined in the supplied measure specification */ public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); }

View的measure和onMeasure方法通过getRootMeasureSpec来得到DecorView的widthMeasureSpec和heightMeasureSpec之后,就需要来设置DecorView的大小了,也就是调用:
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

发现这个measure是View的方法,如下所示:
/** * < p> * This is called to find out how big a view should be. The parent * supplies constraint information in the width and height parameters. * < /p> * * < p> * The actual measurement work of a view is performed in * {@link #onMeasure(int, int)}, called by this method. Therefore, only * {@link #onMeasure(int, int)} can and must be overridden by subclasses. * < /p> * * * @param widthMeasureSpec Horizontal space requirements as imposed by the *parent * @param heightMeasureSpec Vertical space requirements as imposed by the *parent * * @see #onMeasure(int, int) */ public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ........... onMeasure(widthMeasureSpec, heightMeasureSpec); ........... }

通过注释可以看出,这个方法是用来计算当前View应该为多大,也就是实际的宽高。widthMeasureSpec和heightMeasureSpec是由父View传入的约束信息,代表了父View给当前View的测量规格,当前View的宽高是由父View和自身一起决定的。measure方法是final的,不可重载,实际的测量过程是在onMeasure方法里面完成了,因此子类必须且只能重载onMeasure方法来实现自身的测量逻辑。
接下来看onMeasure方法:
/** * < p> * Measure the view and its content to determine the measured width and the * measured height. This method is invoked by {@link #measure(int, int)} and * should be overridden by subclasses to provide accurate and efficient * measurement of their contents. * < /p> * * < p> * < strong> CONTRACT:< /strong> When overriding this method, you * < em> must< /em> call {@link #setMeasuredDimension(int, int)} to store the * measured width and height of this view. Failure to do so will trigger an * < code> IllegalStateException< /code> , thrown by * {@link #measure(int, int)}. Calling the superclass‘ * {@link #onMeasure(int, int)} is a valid use. * < /p> * * < p> * The base class implementation of measure defaults to the background size, * unless a larger size is allowed by the MeasureSpec. Subclasses should * override {@link #onMeasure(int, int)} to provide better measurements of * their content. * < /p> * * < p> * If this method is overridden, it is the subclass‘s responsibility to make * sure the measured height and width are at least the view‘s minimum height * and width ({@link #getSuggestedMinimumHeight()} and * {@link #getSuggestedMinimumWidth()}). * < /p> * * @param widthMeasureSpec horizontal space requirements as imposed by the parent. *The requirements are encoded with *{@link android.view.View.MeasureSpec}. * @param heightMeasureSpec vertical space requirements as imposed by the parent. *The requirements are encoded with *{@link android.view.View.MeasureSpec}. * * @see #getMeasuredWidth() * @see #getMeasuredHeight() * @see #setMeasuredDimension(int, int) * @see #getSuggestedMinimumHeight() * @see #getSuggestedMinimumWidth() * @see android.view.View.MeasureSpec#getMode(int) * @see android.view.View.MeasureSpec#getSize(int) */ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }

注释已经写的非常明白了,子类必须复写onMeasure方法,且最终通过调用setMeasuredDimension方法来存储当前View测量得到的宽和高。这个宽和高是通过getDefaultSize方法得来的,如下所示:
/** * Utility to return a default size. Uses the supplied size if the * MeasureSpec imposed no constraints. Will get larger if allowed * by the MeasureSpec. * * @param size Default size for this view * @param measureSpec Constraints imposed by the parent * @return The size this view should be. */ 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; }

可以看出,如果specMode等于AT_MOST或者EXACTLY就返回specSize,也就是父类指定的specSize,否则返回通过getSuggestedMinimumWidth和getSuggestedMinimumHeight得到的size,从名字可以看出是建议的最小宽度和高度,代码如下所示:
protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); } protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }

可以看出,建议的最小宽度和高度是由view的background以及其mMinWidth、mMinHeight共同决定的。
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); }private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }

可以看出这个方法就是给mMeasuredHeight和mMeasuredWidth进行赋值。进行了赋值之后调用View 的getMeasuredWidth和getMeasuredHeight方法才能得到其正确的测量宽高!
ViewGroup的measure过程上面提到View的measure方法传入的widthMeasureSpec和heightMeasureSpec是由父View传入的约束信息,那么这些信息是何时传入的呢?由于View是嵌套的,因此measure过程也是递归传递的,子View的measure是由父类调用的,然后子View根据传入的父类约束来设置自身的测量规格。
继承自ViewGroup的视图均需要实现onMeasure方法,在这个方法里面对其子View进行测量,同时也对自身进行测量,比如LinearLayout的onMeasure方法如下:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); } }

根据布局的方向分别调用measureHorizontal和measureVertical方法。
在ViewGroup中定义了measureChildren, measureChild, measureChildWithMargins方法来对子视图进行测量。measureChildren内部循环调用了measureChild。
measureChild和measureChildWithMargins的区别在于measureChildWithMargins把child的margin也考虑在内。下面来对measureChildWithMargins方法来分析:
/** * Ask one of the children of this view to measure itself, taking into * account both the MeasureSpec requirements for this view and its padding * and margins. The child must have MarginLayoutParams The heavy lifting is * done in getChildMeasureSpec. * * @param child The child to measure * @param parentWidthMeasureSpec The width requirements for this view * @param widthUsed Extra space that has been used up by the parent *horizontally (possibly by other children of the parent) * @param parentHeightMeasureSpec The height requirements for this view * @param heightUsed Extra space that has been used up by the parent *vertically (possibly by other children of the parent) */ protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //子视图的测量规格是由父视图的测量测量规格以及子视图的LayoutParams来共同决定的 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); //调用子视图的measure方法来设置子视图的测量规格 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }

从以上代码可以看出:子视图的测量规格是由父视图的测量测量规格以及子视图的LayoutParams来共同决定的,因此关键函数是getChildMeasureSpec函数,如下所示:
/** * Does the hard part of measureChildren: figuring out the MeasureSpec to * pass to a particular child. This method figures out the right MeasureSpec * for one dimension (height or width) of one child view. * * The goal is to combine information from our MeasureSpec with the * LayoutParams of the child to get the best possible results. For example, * if the this view knows its size (because its MeasureSpec has a mode of * EXACTLY), and the child has indicated in its LayoutParams that it wants * to be the same size as the parent, the parent should ask the child to * layout given an exact size. * * @param spec The requirements for this view * @param padding The padding of this view for the current dimension and *margins, if applicable * @param childDimension How big the child wants to be in the current *dimension * @return a MeasureSpec integer for the child */ public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); //得到父视图的mode int specSize = MeasureSpec.getSize(spec); //得到父视图的size //得到Parent视图剩余的大小 int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; //根据Parent视图的specMode来进行分支判断 switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY://父类是精确模式 if (childDimension > = 0) { //子视图是精确模式,直接设置了精确的大小(在xml当中设置了layout_width="xxx"或者在代码中设置了具体的数值),子视图的size就是精确值,子视图的mode就是EXACTLY resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { //如果子视图的layout_width或者layout_height为MATCH_PARENT,也就是为父视图的大小,那么子视图的size就是Parent视图剩余的大小,且mode与父类相同,也为EXACTLY // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { //如果子视图的layout_width或者layout_height为WRAP_CONTENT,也就是不超过父视图的大小,那么子视图的size为size,且mode为AT_MOST。 // 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) { //子视图是精确模式,直接设置了精确的大小(在xml当中设置了layout_width="xxx"或者在代码中设置了具体的数值),子视图的size就是精确值,子视图的mode就是EXACTLY // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { //如果子视图的layout_width或者layout_height为MATCH_PARENT,也就是为父视图的大小,那么子视图的size就是Parent视图剩余的大小,且mode与父类相同,也是AT_MOST。 // 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) { //如果子视图的layout_width或者layout_height为WRAP_CONTENT,也就是不超过父视图的大小,那么子视图的size为size,且mode为AT_MOST。 // 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; } // 将resultSize和resultMode进行组装为32为整数返回 //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }

可以看到,getChildMeasureSpec就是根据父视图的specSize和specMode以及child视图的LayoutParams来确定子视图的resultSize和resultMode,然后把resultSize和resultMode进行组装成32位的整数,作为child.measure的参数来对子视图进行测量。
有一个需要特别注意的地方:
  • 当childDimension == LayoutParams.WRAP_CONTENT的时候,其specSize和specMode分别为父视图的size和MeasureSpec.AT_MOST。
  • 再回到上面的View测量过程当中的getDefaultSize方法,如下所示。我们发现当View的specMode为AT_MOST的时候,其size默认就是parent视图的size!
  • 因此,在我们自定义View的时候,需要考虑当specMode为AT_MOST的时候(也就是在xml布局当中设置为WRAP_CONTENT的时候)给当前View的宽高设置一个具体的值,大家可以去看看比如TextView的源代码,均对WRAP_CONTENT的情况进行了特殊的处理!
public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); ...... case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }

以上就是View和ViewGroup的measure过程,在ViewGroup的实现视图当中递归调用子视图的的measure方法来实现整个View树的测量。在自定义View的时候,当我们需要对View的尺寸进行更改的时候,需要实现onMeasure方法,在里面根据父视图给的specSize和specMode来设置当前View的specMode和specSize,需要注意的是当父视图给的specMode==AT_MOST的时候,需要给当前View的宽高设置一个具体的值。
View的layout过程讲完了View的measure过程,接下来就是layout过程。那么这个layout过程是干什么的呢?在measure过程当中设置了view的宽高,那么设置了宽高之后,具体view是显示在屏幕的哪个位置呢?这个就是layout过程干的事。
layout跟measure一样,也是递归结构,来看下View的layout方法:
/** * Assign a size and position to a view and all of its * descendants * * < p> This is the second phase of the layout mechanism. * (The first is measuring). In this phase, each parent calls * layout on all of its children to position them. * This is typically done using the child measurements * that were stored in the measure pass().< /p> * * < p> Derived classes should not override this method. * Derived classes with children should override * onLayout. In that method, they should * call layout on each of their children.< /p> * * @param l Left position, relative to parent * @param t Top position, relative to parent * @param r Right position, relative to parent * @param b Bottom position, relative to parent */ @SuppressWarnings({"unchecked"}) public void layout(int l, int t, int r, int b) { if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 & = ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; }int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; //setFrame方法把参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量 //判断布局是否发生改变 boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); ........ } ...... }

在layout方法里面首先通过setFrame来设置自身的位置,然后调用了onLayout方法,是不是跟measure方法里面调用onMeasure方法类似!来看下onLayout方法:
/** * Called from layout when this view should * assign a size and position to each of its children. * * Derived classes with children should override * this method and call layout on each of * their children. * @param changed This is a new size or position for this view * @param left Left position, relative to parent * @param top Top position, relative to parent * @param right Right position, relative to parent * @param bottom Bottom position, relative to parent */ protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }

发现onLayout是一个空方法,通过注释可以看出:具有子视图的子类需要重写这个onLayout方法并且调用其每一个子视图的layout方法。
这就完全明白了:也就是说直接或者间接继承自ViewGroup的视图需要重写onLayout方法,然后调用其每个子视图的layout方法来设置子视图的位置!我们可以查看LinearLayout,其肯定是实现了onLayout方法,在这个方法里面来一一设置子视图的位置!LinearLayout的onLayout方法如下所示:
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { layoutVertical(l, t, r, b); } else { layoutHorizontal(l, t, r, b); } }

来看下layoutVertical方法:
/** * Position the children during a layout pass if the orientation of this * LinearLayout is set to {@link #VERTICAL}. * * @see #getOrientation() * @see #setOrientation(int) * @see #onLayout(boolean, int, int, int, int) * @param left * @param top * @param right * @param bottom */ void layoutVertical(int left, int top, int right, int bottom) { final int paddingLeft = mPaddingLeft; int childTop; int childLeft; // Where right end of child should go final int width = right - left; int childRight = width - mPaddingRight; //child可以使用的空间 // Space available for child int childSpace = width - paddingLeft - mPaddingRight; //得到 child的个数 final int count = getVirtualChildCount(); final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; //根据majorGravity计算childTop的位置 switch (majorGravity) { case Gravity.BOTTOM: // mTotalLength contains the padding already childTop = mPaddingTop + bottom - top - mTotalLength; break; // mTotalLength contains the padding already case Gravity.CENTER_VERTICAL: childTop = mPaddingTop + (bottom - top - mTotalLength) / 2; break; case Gravity.TOP: default: childTop = mPaddingTop; break; } // 开始进行遍历child视图 for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) {//child不为GONE,因为GONE是不占空间的 final int childWidth = child.getMeasuredWidth(); // 得到onMeasure之后的测量宽度 final int childHeight = child.getMeasuredHeight(); // 得到onMeasure之后的测量高度final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); int gravity = lp.gravity; if (gravity < 0) { gravity = minorGravity; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); // 根据absoluteGravity计算childLeft的值 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = paddingLeft + ((childSpace - childWidth) / 2) + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: childLeft = childRight - childWidth - lp.rightMargin; break; case Gravity.LEFT: default: childLeft = paddingLeft + lp.leftMargin; break; }if (hasDividerBeforeChildAt(i)) { childTop += mDividerHeight; }childTop += lp.topMargin; //通过setChildFrame函数来设置child的位置, setChildFrame函数如下所示 setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); } } }private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height); }

上面这个方法还是比较易懂的,主要就是调用child的layout方法来设置child的位置,当我们给一个View设置好位置之后,其内部的四个变量
mLeft、mTop、mRight和mBottom也就确定了,不过要注意这些值都是相对父视图而言的,而不是相对整个屏幕而言的。这个四个变量是通过以下方式获取的。
public final int getWidth() { return mRight - mLeft; }public final int getHeight() { return mBottom - mTop; }public final int getLeft() { return mLeft; }public final int getRight() { return mRight; }public final int getTop() { return mTop; }public final int getBottom() { return mBottom; }

在View当中还有下面两个函数,这也解释了为什么有时候getWidth()和getMeasuredWidth()以及getHeight()和getMeasuredHeight()会得到不同的值的原因。
public final int getMeasuredWidth() { return mMeasuredWidth & MEASURED_SIZE_MASK; }public final int getMeasuredHeight() { return mMeasuredHeight & MEASURED_SIZE_MASK; }

以上就是View的layout过程,layout相对measure过程来说还是算比较简单的。
* 总结起来就是:直接或者间接继承自ViewGroup的视图需要重写onLayout方法,然后调用其每个子视图的layout方法来设置子视图的位置。*
View的draw过程讲完了View的layout流程,接下来就是draw流程,draw负责对view进行绘制。在ViewRootImpl的drawSoftware方法当中:
/** * @return true if drawing was successful, false if an error occurred */ private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) {// Draw with software renderer. final Canvas canvas; try { final int left = dirty.left; final int top = dirty.top; final int right = dirty.right; final int bottom = dirty.bottom; canvas = mSurface.lockCanvas(dirty); ................ mView.draw(canvas); ......... return true; }

在上述方法当中调用了mView的draw方法,来看View的draw方法:
/** * Manually render this view (and all of its children) to the given Canvas. * The view must have already done a full layout before this function is * called.When implementing a view, implement * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method. * If you do need to override this method, call the superclass version. * * @param canvas The Canvas to which the View is rendered. */ @CallSuper public void draw(Canvas canvas) { ............... /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * *1. Draw the background *2. If necessary, save the canvas‘ layers to prepare for fading *3. Draw view‘s content *4. Draw children *5. If necessary, draw the fading edges and restore layers *6. Draw decorations (scrollbars for instance) */// Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } ........ // skip step 2 & 5 if possible (common case) ....... // Step 2, save the canvas‘ layersif (drawTop) { canvas.saveLayer(left, top, right, top + length, null, flags); } ........ // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers final Paint p = scrollabilityCache.paint; final Matrix matrix = scrollabilityCache.matrix; final Shader fade = scrollabilityCache.shader; if (drawTop) { matrix.setScale(1, fadeHeight * topFadeStrength); matrix.postTranslate(left, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, top, right, top + length, p); } ............... // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); }

通过注释可以看出整个绘制过程分为6部分,在大多数情况下第2步和第5步可以跳过,在自定义View的时候需要实现onDraw方法而不是实现draw方法。
接下来对剩下的四步进行分析:
第一步:绘制背景 通过调用drawBackground方法实现
private void drawBackground(Canvas canvas) { final Drawable background = mBackground; if (background == null) { return; }setBackgroundBounds(); ............... final int scrollX = mScrollX; final int scrollY = mScrollY; if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); } }

如上所示,调用了background的draw方法,也就是Drawable的draw方法。
第三步:绘制内容 通过调用onDraw方法实现
/** * Implement this to do your drawing. * * @param canvas the canvas on which the background will be drawn */ protected void onDraw(Canvas canvas) { }

我们发现onDraw是一个空的方法,需要子类去实现,一般我们在自定义View的时候都会重写onDraw方法来进行绘制。
第四步:绘制子类 通过调用dispatchDraw实现
/** * Called by draw to draw the child views. This may be overridden * by derived classes to gain control just before its children are drawn * (but after its own view has been drawn). * @param canvas the canvas on which to draw the view */ protected void dispatchDraw(Canvas canvas) {}

发现dispatchDraw为空,根据注释:如果View包含子类就需要重写这个方法,那么说明下ViewGroup应该重写了这个方法,看下ViewGroup的dispatchDraw方法,如下所示:
@Override protected void dispatchDraw(Canvas canvas) { ............. for (int i = 0; i < childrenCount; i++) { while (transientIndex > = 0 & & mTransientIndices.get(transientIndex) == i) { final View transientChild = mTransientViews.get(transientIndex); if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() != null) { more |= drawChild(canvas, transientChild, drawingTime); } transientIndex++; if (transientIndex > = transientCount) { transientIndex = -1; } }final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } ............. }

从上述方法看出主要是遍历child,然后调用child的drawChild方法,来看下drawChild方法:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }

可以看出,在drawChild方法当中调用了child.draw方法来实现子视图的绘制。
第六步:绘制装饰,比如前景色,滚动条等 通过onDrawForeground方法实现
/** * Draw any foreground content for this view. * * < p> Foreground content may consist of scroll bars, a {@link #setForeground foreground} * drawable or other view-specific decorations. The foreground is drawn on top of the * primary view content.< /p> * * @param canvas canvas to draw into */ public void onDrawForeground(Canvas canvas) { onDrawScrollIndicators(canvas); onDrawScrollBars(canvas); ........ final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; ........ foreground.draw(canvas); }

可以看出主要是对滚动条和前景色进行绘制。
到此,View绘制的三个基本流程:measure,layout,draw就讲完了,measure过程应该是三个流程里面最为复杂的。希望通过本次对源码的剖析,能够对View的绘制流程有一个清楚的认识,在以后自定义View的时候能够少走弯路~~
View树的重绘还记得在上一篇博客中我们讲ViewGroup#addView方法会导致View树的重新绘制,代码如下所示:
public void addView(View child, int index, LayoutParams params) { if (DBG) { System.out.println(this + " addView"); } if (child == null) { throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup"); } // addViewInner() will call child.requestLayout() when setting the new LayoutParams // therefore, we call requestLayout() on ourselves before, so that the child‘s request // will be blocked at our level requestLayout(); invalidate(true); addViewInner(child, index, params, false); }

其实归根结底是调用了requestLayout和invalidate方法的原因,导致View进行重新绘制,下面来对这两个方法进行分析:
View的requestLayout方法:requestLayout是view的方法,如下所示:
@CallSuper public void requestLayout() { ............ if (mParent != null & & !mParent.isLayoutRequested()) { mParent.requestLayout(); } ......... }

核心代码是mParent.requestLayout,这个方法就会一层层的往上递归,一直到ViewRootImpl的requestLayout。
ViewRootImpl的requestLayout方法在上一篇博客中已经分析过,这个方法会导致整个View树的重绘。
View的invalidate方法:
public void invalidate() { invalidate(true); }void invalidate(boolean invalidateCache) { invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); }void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { ........... // Propagate the damage rectangle to the parent view. final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; if (p != null & & ai != null & & l < r & & t < b) { final Rect damage = ai.mTmpInvalRect; damage.set(l, t, r, b); p.invalidateChild(this, damage); } ........... }

我们发现最终调用了当前view父视图的invalidateChid方法,于是查看ViewGroup的invalidateChid方法:
/** * Don‘t call or override this method. It is used for the implementation of * the view hierarchy. */ @Override public final void invalidateChild(View child, final Rect dirty) { ViewParent parent = this; ............. do { View view = null; if (parent instanceof View) { view = (View) parent; } .......... parent = parent.invalidateChildInParent(location, dirty); if (view != null) { // Account for transform on current parent Matrix m = view.getMatrix(); if (!m.isIdentity()) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); m.mapRect(boundingRect); dirty.set((int) Math.floor(boundingRect.left), (int) Math.floor(boundingRect.top), (int) Math.ceil(boundingRect.right), (int) Math.ceil(boundingRect.bottom)); } } } while (parent != null); } }

我们发现invalidateChild方法里面有一个do-while循环,在这个循环里面循环调用invalidateChildInParent方法,到这里我们自然就可以想到最终会调用ViewRootImpl的invalidateChildInParent方法,ViewRootImpl的invalidateChildInParent方法如下所示:
@Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty); if (dirty == null) { invalidate(); return null; } else if (dirty.isEmpty() & & !mIsAnimating) { return null; } ............ return null; }

可以看到在这个方法里面调用了invalidate方法,如下所示:
void invalidate() { mDirty.set(0, 0, mWidth, mHeight); if (!mWillDrawSoon) { scheduleTraversals(); } }

看到这里是否有一种很熟悉的赶脚(如果看了上一篇博客的话),这个scheduleTraversals方法最终会调用View的三个基本绘制流程来实现整个View树的绘制。
View的postInvalidate方法:当我们想在非ui线程当中刷新View的时候一般都是调用postInvalidate方法,View的postInvalidate方法如下所示:
public void postInvalidate() { postInvalidateDelayed(0); }public void postInvalidateDelayed(long delayMilliseconds) { // We try only with the AttachInfo because there‘s no point in invalidating // if we are not attached to our window final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds); } }

可以看出是调用了ViewRootImpl的dispatchInvalidateDelayed方法:
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds); }

这个方法就是发送一个MSG_INVALIDATE消息到消息队列当中,那肯定是在Handler的handleMessage方法里面对消息进行了处理:
@Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_INVALIDATE: ((View) msg.obj).invalidate(); break;

在handleMessage方法里面调用了View的invalidate方法,而关于invalidate方法,在上面进行了详细的分析。
【Android应用层View绘制流程之measure,layout,draw三步曲】到此为止,对View绘制的三个基本流程从源码的角度进行了详细的剖析,谢谢各位的阅读,不足之处欢迎指出。






    推荐阅读