码迷,mamicode.com
首页 > 移动开发 > 详细

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

时间:2017-04-24 12:21:35      阅读:297      评论:0      收藏:0      [点我收藏+]

标签:getch   war   result   word   javascrip   mat   list   子类   约束   

概述

上一篇博文对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绘制的三个基本流程从源码的角度进行了详细的剖析,谢谢各位的阅读,不足之处欢迎指出。

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

标签:getch   war   result   word   javascrip   mat   list   子类   约束   

原文地址:http://blog.csdn.net/liuyi1207164339/article/details/70545887

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!