Android View的绘制流程之测量、布局、绘制源码(API 26)分析


Android为开发者提供了一个 GUI库,里面有很多控件,但这些控件不能满足需求时,就需要开发者自定义 View了,此时需要了解 View的三大绘制流程:测量( measure)、布局( layout)和绘制( draw)过程。

一、预备知识

首先需要了解一下顶层视图DecorView以及ViewRootImpl对象的创建过程,以及其分发的三大绘制流程。

  • DecorView:应用程序窗口内部所包含的视图对象的实际类型,它集成了View类,作为容器ViewGroup使用;

  • ViewRoot:相当于是MVC模型中的Controller,具有以下职责:

    1. 负责为应用程序窗口视图创建Surface
    2. 配合WindowManagerService来管理系统的应用程序窗口;
    3. 负责管理、布局和渲染应用程序窗口视图的UI。

1.1 顶层视图DecorView以及ViewRootImpl对象的创建过程

Activity组件在启动的过程中,会调用ActivityThread类的成员函数handleLaunchActivity,用来创建以及首激活Activity组件,以此从这个函数开始分析应用程序窗口的视图对象以及其所管理的ViewRoot对象的创建过程,如下图所示:

在这里插入图片描述

上图第11步获取到的就是顶层视图decor,第13、14、15和16步就是将decor传递给ViewRoot,这样ViewRootDecoView就建立了关联。

1.2 顶层视图DecorView分发的三大绘制流程

在1.1图的第15步中,ViewRoot类的成员函数setView会调用ViewRoot类的另一个成员函数requestLayout来请求对顶层视图decorView作第一次布局及显示。下面从ViewRoot类的成员函数requestLayout开始,分析顶层视图decorView的绘制三大流程,如下图所示:

在这里插入图片描述

上图中的第6步会调用ViewRootImpl类的performTraversals方法,performTraversals方法会依次调用performMeasure方法、performLayout方法和performDraw方法来完成顶层视图decorView的测量过程(measure)、布局过程(layout)和绘制过程(draw)。

二、三大绘制流程

2.1 View的测量过程(measure

1.2图第10步会遍历每一个子View,然后调用子Viewmeasure方法(即第11步),继而开始子View的测量过程(measure)。ViewGroup类型的View和非ViewGroup类型的View的测量过程是不同的:非ViewGroup类型的View通过onMeasure方法就完成了其测量过程;而ViewGroup类型的View除了通过onMeasure方法完成自身的测量过程之外,还要再该该方法中完成遍历子Viewmeasure方法,各个子View再去递归执行这个流程。

2.1.1 非ViewGroup类型的View的测量过程

先通过如下的时序图,整体看一下测量流程:

在这里插入图片描述

对于上面的步骤详细分析一下:第一步执行View类中的measure方法,该方法是一个final方法,则意味着子类不能重写该方法。measure方法会调用View类的onMeasure方法,onMeasure方法源码如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                       getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

此代码对应上图的第3、4、5、6、7步,先但第三步对应的View类的getSuggestedMinimumWidth()方法源码:

protected int getSuggestedMinimumWidth() {
  return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

从该方法中可以看到,当View没有设置背景时,此方法的返回值为mMinWidth,该值对应于android:minWidth属性所指定的值,如果未设置此属性,mMinWidth默认为0;当View设置了背景时,该方法返回max(mMinWidth, mBackground.getMinimumWidth()),下面看一下Drawable类中getMinimumWidth方法的源码:

public int getMinimumWidth() {
  final int intrinsicWidth = getIntrinsicWidth();
  return intrinsicWidth > 0 ? intrinsicWidth : 0;
}

由上面的代码可以看出getMinimumWidth返回的是View的背景的原始宽度,如果没有原始宽度则返回0.

getSuggestedMinimumWidth()方法小结:

View未设置背景时,返回android:minWidth属性指定的值(可以为0);
View设置背景时,返回android:minWidth属性指定的值与View背景的最小宽度中的最大值。

下面看一下最关键View类的getDefaultSize方法的源码(对应第4步):

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;
}

对于MeasureSpec.UNSPECIFIED测量模式,一般用于系统内部测量过程,该方法返回getSuggestedMinimumWidth方法得到的值;对于MeasureSpec.AT_MOSTMeasureSpec.EXACTLY这两种测量模式,该方法直接返回测量后的值(即父View通过measure方法传递过来的测量值,也说明了下面注意事项的第一条)。

对于第5、6步,与3、4步类似,不再赘述。

第7步View类的setMeasuredDimension方法调用了第8步中View类的setMeasuredDimensionRaw方法,该方法源码如下:

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
  mMeasuredWidth = measuredWidth;
  mMeasuredHeight = measuredHeight;

  mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

由上面的代码可知,View测量后的宽高被保存到View类的成员变量mMeasuredWidthmMeasuredHeight中了,通过getMeasuredWidthgetMeasuredHeight方法获取到的就是这两个值。需要注意的是,在某些极端情况下,系统可能需要多次measure才能确定最终的测量宽高,此时,在onMeasure方法中拿到的测量宽高很肯能是不准确的,一个好的习惯是在onLayout方法中去获取View最终的测量宽高

上面只是说在自定义View中什么时候获取最终的测量宽高,那么在Activity中什么时机可以获取View的测量宽高呢?《Android开发艺术探索》P190介绍如下:

  1. Activity/View#onWindowFocusChanged方法中调用;
  2. Activity中的onStart方法中执行View.pos获取;
  3. 通过ViewTreeObserver获取;
  4. 通过手动执行View.measure获取。

有如下几点需要注意:

  1. 直接继承View的自定义控件需要重写onMeasure方法,并且需要设置wrap_content时自身的大小,否则在布局中使用wrap_content就相当于使用match_parent
  2. 在自定义View时可以通过重写onMeasure方法设置View测量大小,这样的话你就抛弃了父容器通过measure方法传进来的建议测量值MeasureSpec
2.1.2 ViewGroup类型的View的测量过程

先通过如下的时序图,整体看一下测量流程:

在这里插入图片描述

ViewGroup并没有定义其自身测量的具体过程(即没有onMeasure方法),这是因为ViewGroup是一个抽象类,其测量过程的onMeasure方法需要各个子类去具体实现,所以上图展示了LinearLayout的测量过程图。

对于上图的步骤详细解析一下:第1步执行View类的measure方法,该方法是一个final方法,则意味着子类不能重写该方法,measure方法会调用LinearLayout类的onMeasure方法,该方法源码如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  if (mOrientation == VERTICAL) {
    measureVertical(widthMeasureSpec, heightMeasureSpec);
  } else {
    measureHorizontal(widthMeasureSpec, heightMeasureSpec);
  }
}

现只分析当LinearLayout的方向是垂直方向的情况,此时会执行该类的measureVertical方法,代码如下(仅展示我们关心的的逻辑代码):

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
	//...
  // See how tall everyone is. Also remember max width.
  for (int i = 0; i < count; ++i) {
    final View child = getVirtualChildAt(i);
			//...
      // Determine how big this child would like to be. If this or
      // previous children have given a weight, then we allow it to
      // use all available space (and we will shrink things later
      // if needed).
      //...
      measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                               heightMeasureSpec, usedHeight);

      final int childHeight = child.getMeasuredHeight();
			//...

      final int totalLength = mTotalLength;
      mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                              lp.bottomMargin + getNextLocationOffset(child));
			//...
    }
	//...
  
  // Add in our padding
  mTotalLength += mPaddingTop + mPaddingBottom;
  int heightSize = mTotalLength;
  // Check against our minimum height
  heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
  // Reconcile our calculated size with the heightMeasureSpec
  int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
  heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
  //...
  setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                       heightSizeAndState);
	//...
}

由上面的代码可知LinearLayout类的measureVertical方法会遍历每一个子元素并且执行LinearLayout类的measureChildBeforeLayout方法对子元素进行测量,该方法内部会执行子元素的measure方法。源码中变量mTotalLength用来存放LinearLayout竖直方向上的当前高度,每遍历一个子元素,mTotalLength都会增加,增加的部分主要包括子元素自身的高度以及其在竖直方向上的margin。当测量完所有子元素后,LinearLayout会根据子元素的情况测量自身的大小,针对竖直的LinearLayout来说,它在水平方向的测量过程遵循View的测量过程,在竖直方向上有所不同:如果它的布局高度采取match_parent或具体的数值,那么其测量过程与View一致;如果它的布局中高度采用的是wrap_content,则其高度是所有子元素所占用的高度总和,但是仍然不能超过父容器的剩余空间,这个过程对应于resolveSizeAndState的源码:

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
  final int specMode = MeasureSpec.getMode(measureSpec);
  final int specSize = MeasureSpec.getSize(measureSpec);
  final int result;
  switch (specMode) {
    case MeasureSpec.AT_MOST:
      if (specSize < size) {
        result = specSize | MEASURED_STATE_TOO_SMALL;
      } else {
        result = size;
      }
      break;
    case MeasureSpec.EXACTLY:
      result = specSize;
      break;
    case MeasureSpec.UNSPECIFIED:
    default:
      result = size;
  }
  return result | (childMeasuredState & MEASURED_STATE_MASK);
}

下面来看一下LinearLayout类的measureChildBeforeLayout方法是如何测量子元素的?该方法的第4个和第6个参数分别代表在水平方向和垂直方向上LinearLayout已经被其它子元素占据的长度,该方法源码如下:

void measureChildBeforeLayout(View child, int childIndex,
                              int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
                              int totalHeight) {
  measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                          heightMeasureSpec, totalHeight);
}

measureChildBeforeLayout方法会调用ViewGroup类的measureChildWidthMargins方法,该方法源码如下:

    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);
    }

ViewGroup类的measureChildWithMargins方法会调用子元素的measure方法对子元素进行测量。在对子元素测量之前会通过调用ViewGroup类的getChildMeasureSpec方法得到子元素宽高的MeasureSpec,从该方法的前两个参数可知,子元素MeasureSpec的创建与父容器的MeasureSpec、父容器的padding、子元素的margin和兄弟元素所占用的长度有关。该方法源码如下:

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;
  }
  //noinspection ResourceType
  return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

ViewGroup类的getChildMeasureSpec方法的逻辑可以通过下表来说明(表中的parentSize是指父容器目前可使用的大小,参考《Android开发艺术探索》182页):

childLayoutParams\parentSpecModeEXACTLYAT_MOSTUNSPECIFIED
dp/pxEXACTLY (childSize)EXACTLY (childSize)EXACTLY (childSize)
match_parentEXACTLY (parentSize)AT_MOST (parentSize)UNSPECIFIED (0)
wrap_contentAT_MOST (parentSize)AT_MOST (parentSize)UNSPECIFIED (0)

ViewGroup类的getChildMeasureSpec方法返回子元素宽高的MeasureSpec,然后将子元素宽高的MeasureSpec作为measure方法的参数。

小结:

  1. View会遍历测量每一个子View(通常会使用ViewGroup类的measureChildWidthMargins方法),然后调用子Viewmeasure方法并且将测量后的宽高作为measure方法的参数,但这只是父View的建议值,子View可以通过重写onMeasure方法来改变测量值;
  2. ViewGroup类型的View自身的测量是在非ViewGroup类型的ViewonMeasure方法中进行测量的;
  3. ViewGroup类型的View自身的测量是在ViewGroup类型的ViewonMeasure方法中进行测量的;
  4. 直接继承ViewGroup的自定义控件需要重写onMeasure方法并且设置wrap_content时自身的大小,否则布局中使用wrap_content就相当于使用match_parent,具体原因可以通过上面的表格说明。

2.2 View的布局过程(layout

1.2图的第17步会遍历每一个子元素,并且调用子元素的layout方法,继而开始进行子元素的布局过程。layout过程相较于measure过程比较简单,layout方法用来确定View本身的位置,而onLayout方法用来确定所有子元素的位置。ViewGroup类型的View和非ViewGroup类型的View的布局过程是不同的:非ViewGroup类型的View通过layout方法就完成了其布局过程;而ViewGroup类型的View除了通过layout方法完成自身布局过程外,还需要调用onLayout方法去遍历子元素并调用子元素的layout方法,各个子View再递归执行这个流程。

2.2.1 非ViewGroup类型的View的布局过程

先通过如下的时序图,整体看一下布局过程:

在这里插入图片描述

对上面的时序图分析一下,第一步执行View类的layout方法,源码如下:

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;

  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);

    if (shouldDrawRoundScrollbar()) {
      if(mRoundScrollbarRenderer == null) {
        mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
      }
    } else {
      mRoundScrollbarRenderer = null;
    }

    mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnLayoutChangeListeners != null) {
      ArrayList<OnLayoutChangeListener> listenersCopy =
        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
      int numListeners = listenersCopy.size();
      for (int i = 0; i < numListeners; ++i) {
        listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
      }
    }
  }

  mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
  mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

  if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
    mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
    notifyEnterOrExitForAutoFillIfNeeded(true);
  }
}

如果isLayoutModeOptical(mParent)方法返回true,那么就会执行setOpticalFrame方法,否则会执行setFrame方法,并且setOpticalFrame方法内部会执行setFrame方法,所以无论如何setFrame方法都会执行的。

第二步layout方法会调用View类的setFrame方法,部分关键代码如下:

protected boolean setFrame(int left, int top, int right, int bottom) {
  boolean changed = false;
	//...
  if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
    changed = true;
		//...
    int oldWidth = mRight - mLeft;
    int oldHeight = mBottom - mTop;
    int newWidth = right - left;
    int newHeight = bottom - top;
    boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

    // Invalidate our old position
    invalidate(sizeChanged);

    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;
		//...

    if (sizeChanged) {
      sizeChange(newWidth, newHeight, oldWidth, oldHeight);
    }
    //...
  }
  return changed;
}

由上面代码可知,setFrame方法是用来设置View的四个顶点的位置的,即初始化mLeftmTopmRightmBottom这四个值。View的顶点一旦确定,那么View在父容器中的位置也就确定了。

第三步layout方法接着调用View类的onLayout方法,这个方法的作用是来确定子元素的位置的。由于非ViewGroup类型的View没有子元素,所以View类的onLayout方法为空。

2.2.2 ViewGroup类型的View的布局过程

先通过如下的时序图,整体看一下布局过程:

在这里插入图片描述

上图为LinearLayout布局的时序图,因为ViewGrouponLayout方法是抽象方法,所以选择其子类LinearLayout进行分析。

对于上面的时序图,第一步执行ViewGrouplayout方法,该方法是一个final方法,即子类无法重写该方法。其源码如下:

@Override
public final void layout(int l, int t, int r, int b) {
  if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
    if (mTransition != null) {
      mTransition.layoutChange(this);
    }
    super.layout(l, t, r, b);
  } else {
    // record the fact that we noop'd it; request layout when transition finishes
    mLayoutCalledWhileSuppressed = true;
  }
}

第二步ViewGroup类的layout方法会调用View类的layout方法,第三步View类的layout方法调用View类的setFrame方法,这两步与上面讨论的ViewGroup类型的View的布局过程的第一、二步相同,不再赘述。直接看第四步View类的layout方法调用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);
  }
}

我们只分析当LinearLayout的方向是垂直方向的情况,此时会执行LinearLayout类的layoutVertical方法,源码如下:

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;

  // Space available for child
  int childSpace = width - paddingLeft - mPaddingRight;

  final int count = getVirtualChildCount();

  final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
  final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

  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;
  }

  for (int i = 0; i < count; i++) {
    final View child = getVirtualChildAt(i);
    if (child == null) {
      childTop += measureNullChild(i);
    } else if (child.getVisibility() != GONE) {
      final int childWidth = child.getMeasuredWidth();
      final int childHeight = child.getMeasuredHeight();

      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);
      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, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
      childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

      i += getChildrenSkipCount(child, i);
    }
  }
}

可以看到LinearLayout类的onLayout方法会遍历每一个子元素,然后调用该类的setChildFrame方法,setChildFrame方法又会调用子元素的layout方法来对子元素进行布局,其源码如下:

private void setChildFrame(View child, int left, int top, int width, int height) {
  child.layout(left, top, left + width, top + height);
}

2.3 View的绘制过程(draw

1.2图的第26步会遍历每一个子View,并且调用子元素的draw方法,继而开始进行子View的绘制过程。

先通过如下时序图,整体看一下绘制过程:

在这里插入图片描述

上图是LinearLayout的绘制时序图,因为ViewonDraw方法是空方法,所以选择ViewGroup的子类LinearLayout进行分析。

LinearLayout的绘制过程遵循如下步骤:

  1. 绘制背景;
  2. 绘制自己(绘制分割线);
  3. 绘制子ViewdispatchDraw);
  4. 绘制前景。

Android中是通过View类的draw方法来实现上述的4步的,其源码如下:

/**
 * 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) {
  final int privateFlags = mPrivateFlags;
  final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
    (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
  mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

  /*
   * 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)
  final int viewFlags = mViewFlags;
  boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
  boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
  if (!verticalEdges && !horizontalEdges) {
    // Step 3, draw the content
    if (!dirtyOpaque) onDraw(canvas);

    // Step 4, draw the children
    dispatchDraw(canvas);

    drawAutofilledHighlight(canvas);

    // Overlay is part of the content and draws beneath Foreground
    if (mOverlay != null && !mOverlay.isEmpty()) {
      mOverlay.getOverlayView().dispatchDraw(canvas);
    }

    // Step 6, draw decorations (foreground, scrollbars)
    onDrawForeground(canvas);

    // Step 7, draw the default focus highlight
    drawDefaultFocusHighlight(canvas);

    if (debugDraw()) {
      debugDrawFocus(canvas);
    }

    // we're done...
    return;
  }
  //...
}

从这个方法的注释可知:当自定义View需要绘制时,应该实现View类的onDraw方法而不是重新该类的draw方法;如果的确需要重新draw方法,必须在重写时调用父类的draw方法。

上面的代码很明显地说明了View绘制过程的4步。由于View类无法确定自己是否有子元素,所以View类的dispatchDraw方法是空的,我们来看ViewGroup类的dispatchDraw方法的源码(我们截取部分感兴趣的代码):

@Override
protected void dispatchDraw(Canvas canvas) {
  boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
  final int childrenCount = mChildrenCount;
  final View[] children = mChildren;
	//...

  boolean more = false;
  final long drawingTime = getDrawingTime();

  if (usingRenderNodeProperties) canvas.insertReorderBarrier();
  final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
  int transientIndex = transientCount != 0 ? 0 : -1;
  // Only use the preordered list if not HW accelerated, since the HW pipeline will do the
  // draw reordering internally
  final ArrayList<View> preorderedList = usingRenderNodeProperties
    ? null : buildOrderedChildList();
  final boolean customOrder = preorderedList == null
    && isChildrenDrawingOrderEnabled();
  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);
    }
  }
 	//...
}

该方法会遍历每一个子元素,然后调用ViewGroup类的drawChild方法对子元素进行绘制,其源码如下:

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
  return child.draw(canvas, this, drawingTime);
}

三、与View生命周期相关的常用的回调方法

  • onFocusChanged(boolean, int, android.graphics.Rect):该方法在当前View获得或失去焦点时被回调;
  • onWindowFocusChanged(boolean):该方法在包含当前Viewwindow获得或失去焦点时被回调;
  • onAttachedToWindow():该方法在当前View被附到一个window上时被调用;
  • onDetachedFromWindow():该方法在当前View从一个window上分离时被调用;
  • onVisibilityChanged(View, int):该方法在当前View或其祖先的可见性改变时被调用;
  • onWindowVisibilityChanged(int):该方法在包含当前Viewwindow可见性改变时被回调。

四、参考资料

  1. Android应用程序窗口(Activity)的视图对象(View)的创建过程分析
  2. Android View的测量、布局、绘制流程源码分析及自定义View实例演示
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值