zoukankan      html  css  js  c++  java
  • 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' layers
    
              if (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方法,在上面进行了详细的分析。

    到此为止。对View绘制的三个基本流程从源代码的角度进行了详细的剖析,谢谢各位的阅读。不足之处欢迎指出。

  • 相关阅读:
    SQL对Xml字段的操作
    五种常见的ASP.NET安全缺陷
    EntityFramework中常用的数据删除方式
    002_ASP.NET 换主题
    001_ASP.NET MVC 实用教程 论坛项目 北盟网校 原创视频教程
    LINQ to Entities 比较日期
    windows10多桌面创建 切换 和分屏
    winform的combox下拉框绑定数据源
    C# 怎么让winform程序中的输入文本框保留上次的输入
    dos 批量重命名 bat
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/8516364.html
Copyright © 2011-2022 走看看