Android高级UI开发(三十五)measure测试过程

举报
yd_57386892 发表于 2020/12/28 23:51:24 2020/12/28
【摘要】          android View绘制流程分为3个步骤:分别是measure、  layout、  draw 。今天我们先来探究一下measure的过程。在上一节android开发笔记(三十四)中,我们研究了DecorView绘制到PhoneWindow上的流程,也就是View绘制的概况性流程(DecorView extends View), 我们回顾一下那个流程图:...

         android View绘制流程分为3个步骤:分别是measure、  layout、  draw 。今天我们先来探究一下measure的过程。在上一节android开发笔记(三十四)中,我们研究了DecorView绘制到PhoneWindow上的流程,也就是View绘制的概况性流程(DecorView extends View), 我们回顾一下那个流程图:

 

今天我们要研究的就是measure阶段,上图中用红线圈住的部分。performMeasure函数会执行View的measure函数,而measure函数又会执行onMeasure函数。下面我们来分析一下这些测量相关的函数源码。

1. View.measure函数

代码如下:


      /**
       * <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) {
      boolean optical = isLayoutModeOptical(this);
      if (optical != isLayoutModeOptical(mParent)) {
       Insets insets = getOpticalInsets();
      int oWidth  = insets.left + insets.right;
      int oHeight = insets.top  + insets.bottom;
       widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
       heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
       }
      // Suppress sign extension for the low bytes
      long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
      if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
      final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
      // Optimize layout by avoiding an extra EXACTLY pass when the view is
      // already measured as the correct size. In API 23 and below, this
      // extra pass is required to make LinearLayout re-distribute weight.
      final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
       || heightMeasureSpec != mOldHeightMeasureSpec;
      final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
       && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
      final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
       && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
      final boolean needsLayout = specChanged
       && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
      if (forceLayout || needsLayout) {
      // first clears the measured dimension flag
       mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
       resolveRtlPropertiesIfNeeded();
      int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
      if (cacheIndex < 0 || sIgnoreMeasureCache) {
      // measure ourselves, this should set the measured dimension flag back
       onMeasure(widthMeasureSpec, heightMeasureSpec);
       mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
       } else {
      long value = mMeasureCache.valueAt(cacheIndex);
      // Casting a long to int drops the high 32 bits, no mask needed
       setMeasuredDimensionRaw((int) (value >> 32), (int) value);
       mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
       }
      // flag not set, setMeasuredDimension() was not invoked, we raise
      // an exception to warn the developer
      if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
      throw new IllegalStateException("View with id " + getId() + ": "
       + getClass().getName() + "#onMeasure() did not set the"
       + " measured dimension by calling"
       + " setMeasuredDimension()");
       }
       mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
       }
       mOldWidthMeasureSpec = widthMeasureSpec;
       mOldHeightMeasureSpec = heightMeasureSpec;
       mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
       (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
       }
  
 

 1.1  参数解释

void measure(int widthMeasureSpec, int heightMeasureSpec) 先看这个函数的2个参数:

   1.1.1 widthMeasureSpec参数

           int widthMeasureSpec:父容器给的测量规格(宽度),用于测量当前子View的宽度,也就是说在测量View时不但要考虑自身内容的宽度,还要考虑父容器的限制, 例如父容器要求子View的宽度最大不能超过父容器它自身的宽度,这就是一个限制,这个宽度限制用这个参数 ”widthMeasureSpec“ 来表示,这个int型参数总共占4个字节32位,高2位代表”测量模式“,后30位是父容器给子View指定的宽度大小。也就是说widthMeasureSpec这个规格,总共由 测试模式与宽度大小组成。这里我们解释一下测量模式,最后随着对测量程序的深入分析,来指出这些测量模式有什么用。

测量模式分为3种:

1.  MeasureSpec.UNSPECIFIED: 未限制,子VIEW想要多大就多大(宽、高),常见的是Scrollview作为父容器,ListView控件。

2. MeasureSpec.AT_MOST:限制子View的大小最大不能超过父容器的大小specialSize,specialSize就是widthMeasureSpec中低30个bit所表示的大小(包括宽、高)。

3. MeasureSpec.EXACTLY:父容器已经测出了子VIEW的大小, 为子VIEW指定了一个精确值,这个值就是specialSize。通常子VIEW的宽高属性是一个确定值或match_parent。

   1.1.2 heightMeasureSpec参数

            int heightMeasureSpec:父容器给的测量规格(高度),用于测量当前子View的高度,它的概念可以参考上述   widthMeasureSpec,也有3种测试模式,在此不再赘述。测量规格暂且先简单介绍到这里,我们继续分析测量过程,在分析代码的过程中理解MeasureSpec上述View的measure方法中可以得知最终调用了onMeasure(widthMeasureSpec,heightMeasureSpec),测量当前View的核心代码都在这个函数里,我们可以看到在这里将widthMeasureSpec和heightMeasureSpec续传给了onMeasure。

 

2. View.onMeasure


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

   2.1 参数解释

     参数:widthMeasureSpec,heightMeasureSpec,父容器给的测量约束(测量规格);

     widthMeasureSpec:horizontal space requirements as imposed by the parent.  水平空间上的约束,这个约束来自父容器;             heightMeasureSpec:Vertical space requirements as imposed by the parent.     垂直空间上的约束,这个约束来自父容器。

   2.2 onMeasure函数体分析

      这个方法的作用测量当前View,和它里面的内容来决定View的宽、高

         2.2.1 setMeasuredDimension作用

               onMeasure中调用了setMeasuredDimension,用来存储View的宽高的,因为最终调用了setMeasuredDimensionRaw,而在setMeasuredDimensionRaw函数里将measuredWidth和measuredHeight保存在了View的成员变量里,代码如下:


      private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
       mMeasuredWidth = measuredWidth;
       mMeasuredHeight = measuredHeight;
       mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
      }
  
 

      2.2.2 onMeasure深入分析

onMeasure通常会被子类重写,如FrameLayout里就重写了onMeasure函数,用于重新测量自己的宽高以及childview的宽高。那么我们就来分析一下FrameLayout的这个onMeasure函数,先贴出代码如下:


        @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      int count = getChildCount();
      final boolean measureMatchParentChildren =
       MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
       MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
       mMatchParentChildren.clear();
      int maxHeight = 0;
      int maxWidth = 0;
      int childState = 0;
      for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (mMeasureAllChildren || child.getVisibility() != GONE) {
       measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
      final LayoutParams lp = (LayoutParams) child.getLayoutParams();
       maxWidth = Math.max(maxWidth,
       child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
       maxHeight = Math.max(maxHeight,
       child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
       childState = combineMeasuredStates(childState, child.getMeasuredState());
      if (measureMatchParentChildren) {
      if (lp.width == LayoutParams.MATCH_PARENT ||
       lp.height == LayoutParams.MATCH_PARENT) {
       mMatchParentChildren.add(child);
       }
       }
       }
       }
      // Account for padding too
       maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
       maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
      // Check against our minimum height and width
       maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
       maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
      // Check against our foreground's minimum height and width
      final Drawable drawable = getForeground();
      if (drawable != null) {
       maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
       maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
       }
       setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
       resolveSizeAndState(maxHeight, heightMeasureSpec,
       childState << MEASURED_HEIGHT_STATE_SHIFT));
       count = mMatchParentChildren.size();
      if (count > 1) {
      for (int i = 0; i < count; i++) {
      final View child = mMatchParentChildren.get(i);
      final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
      final int childWidthMeasureSpec;
      if (lp.width == LayoutParams.MATCH_PARENT) {
      final int width = Math.max(0, getMeasuredWidth()
       - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
       - lp.leftMargin - lp.rightMargin);
       childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
       width, MeasureSpec.EXACTLY);
       } else {
       childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
       getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
       lp.leftMargin + lp.rightMargin,
       lp.width);
       }
      final int childHeightMeasureSpec;
      if (lp.height == LayoutParams.MATCH_PARENT) {
      final int height = Math.max(0, getMeasuredHeight()
       - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
       - lp.topMargin - lp.bottomMargin);
       childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
       height, MeasureSpec.EXACTLY);
       } else {
       childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
       getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
       lp.topMargin + lp.bottomMargin,
       lp.height);
       }
       child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
       }
       }
       }
  
 

        2.2.2.1 onMeasure参数解释

                  首先这个onMeasure的功能是测量Framelayout这个父布局的宽高,onMeasure的两个参数widthMeasureSpec和heightMeasureSpec是这个FrameLayout的父容器给这个FrameLayout的宽、高约束规格,在这里我们以DecorView为例(DecorView继承于FrameLayout,DecorView的onMeasure最终还是调用了FrameLayout的onMeasure),它的父容器是window窗口,window窗口给DecorView的宽高约束是:大小为windowSize,测量模式为MeasureSpec.EXACTLY,说明DecorView将来的宽高正好是一个确切值:windowSize,即宽高铺满整个屏幕。

下面两行代码就是window窗口为Decorview创建的宽,高规格,我们发现大小为windowSize,模式为EXACTLY

widthMeasureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);

heightMeasureSpec= MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);

那么为什么说在EXACTLY模式下,DecorView的宽高,正好是windowSize呢,这个结论是如何得出的呢?

我们看onMeasure函数中的这段代码:


       setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
       resolveSizeAndState(maxHeight, heightMeasureSpec,
       childState << MEASURED_HEIGHT_STATE_SHIFT));
  
 

这个函数比较熟悉吧,它就是最终用来保存一个容器ViewGroup(或view)的宽高的,将宽高值赋值给了view的成员变量:mMeasuredWidth,mMeasuredHeight;

resolveSizeAndState函数,在这里分别确定了宽高值,我们来分析一下resolveSizeAndState内部代码,就会得出上面那个结论:EXACTLY模式下,DecorView的宽高,正好是windowSize。贴出resolveSizeAndState函数的函数体如下:


       /**
       * Utility to reconcile a desired size and state, with constraints imposed
       * by a MeasureSpec. Will take the desired size, unless a different size
       * is imposed by the constraints. The returned value is a compound integer,
       * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
       * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the
       * resulting size is smaller than the size the view wants to be.
       *
       * @param size How big the view wants to be.
       * @param measureSpec Constraints imposed by the parent.
       * @param childMeasuredState Size information bit mask for the view's
       * children.
       * @return Size information bit mask as defined by
       * {@link #MEASURED_SIZE_MASK} and
       * {@link #MEASURED_STATE_TOO_SMALL}.
       */
      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);
       }
  
 

      final int specMode = MeasureSpec.getMode(measureSpec);
      获取父容器给的宽(高)规格中的测试模式,在这里specMode 是EXACT,因为Window(父容器)给Decorview(子ViewGroup)
      的测试模式就是EXACT ,final int specSize = MeasureSpec.getSize(measureSpec);获取获取父容器给的宽(高)规格中的大小,
      在这里因为测试模式是EXACT,所以将来Decorview的宽(高)就是一个精确值:specSize,代码依据如下:
  
 

          case MeasureSpec.EXACTLY:
                      result = specSize;
                      break;
  
 

Ok,我们总结一下我们上面所讲的,我们主要说明了

onMeasure(int widthMeasureSpec, int heightMeasureSpec)中的两个参数widthMeasureSpec和heightMeasureSpec, 是父容器给的测量规格,用以测量当前FrameLayout(ViewGroup)的宽和高,同时当测量规格中的测量模式为EXACT时, 那么当前FrameLayout(ViewGroup)的宽和高就是测量规格中指定的宽和高,也就是说父容器指示给它的大小。

      2.2.2.2 测量自己先得测量出子child

              1. for循环测量各child的宽高,才能测量出自己的宽高

接下来继续看一下这个FrameLayout(ViewGroup)的测量函数onMeasure,内部有一个for循环,用于测量当前FrameLayout的子View。这里为什么要去测量FrameLayout的子View呢?如果子View又是一个ViewGroup容器类型,那么将继续嵌套递归测量更深层次的子View,直到子View不是一个容器,就可以结束了,并逐渐递归返回了。这里我们先贴出代码再加以分析,for循环测量子View的代码如下:


      for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (mMeasureAllChildren || child.getVisibility() != GONE) {
       measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
      final LayoutParams lp = (LayoutParams) child.getLayoutParams();
       maxWidth = Math.max(maxWidth,
       child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
       maxHeight = Math.max(maxHeight,
       child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
       childState = combineMeasuredStates(childState, child.getMeasuredState());
      if (measureMatchParentChildren) {
      if (lp.width == LayoutParams.MATCH_PARENT ||
       lp.height == LayoutParams.MATCH_PARENT) {
       mMatchParentChildren.add(child);
       }
       }
       }
       }
  
 

首先,我们来回答我们刚才提出的问题,为什么要去测量子View?这是因为当前容器的大小有时候会受子View大小的影响,例如当FrameLaout的宽高属性设置为wrap_content时。FrameLaout的宽应该取各个子View宽度的最大值,同理高度也一样,取Views高度的最大值。

下面这个函数就是用于测量子View的宽高。

 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
 

下面这段代码就是用于获取各个子View的宽高的最大值。


                      final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                      maxWidth = Math.max(maxWidth,
                              child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                      maxHeight = Math.max(maxHeight,
                              child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
  
 

我们再回头看一下这行代码:


      setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
       resolveSizeAndState(maxHeight, heightMeasureSpec,
       childState << MEASURED_HEIGHT_STATE_SHIFT));
  
 


其中resolveSizeAndState函数,给当前FrameLayout的默认宽高正是maxWidth,maxHeight,然后再结合widthMeasureSpec和heightMeasureSpec来共同决定当前FrameLayout容器的最终大小。

 

        2.   measureChildWithMargins分析(深入分析如何测量child)

        measureChildWithMargins配合for循环用来测量FrameLayout的各个子View的宽高,我们来分析它的源码。源码如下:


       /**
       * 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();
      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);
       }
  
 

 

     (1)参数说明 

protected void measureChildWithMargins(

            View child,  : 被测量的child
            int parentWidthMeasureSpec,      父容器的测量宽度规格(即child的  “祖父容器”  给child的父容器的测量规格)

            int widthUsed,                                 child的父容器已被其它子child占用的宽度空间
            int parentHeightMeasureSpec,     父容器的测量高度规格(即child的  “祖父容器”  给child的父容器的测量规格)

            int heightUsed                                  child的父容器已被其它子child占用的高度空间

    (2). 测量child的流程

    1)按照惯例,需先得到child的父容器指定给child的测量规格,childWidthMeasureSpec 和childHeightMeasureSpec 。

measureChildWithMargins函数的如下代码实现了childWidthMeasureSpec和childHeightMeasureSpec的生成。

 


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

我们可以看到getChildMeasureSpec函数,用到了parentWidthMeasureSpec和parentHeightMeasureSpec,可见子child的测量规格的生成离不开它的父容器的测量规格。我们深入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);
      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);
       }
  
 

目前对于DecorView来说,这里的specMode就是EXACT, specSize实质就是child的父容器FrameLayout的宽高。

int size = Math.max(0, specSize - padding);中的size实质就是父容器DecorView留给child的最大空间。
 

childDimension>0 时,说明child的宽,高属性是一个数值,一个确切的值,那么结合父容器DecorView的测量模式为EXACT,会为child生成一个EXACT模式的测量规格,大小就是这个确切值:childDimension; 最终计算出两个测量规格childWidthMeasureSpec 和childHeightMeasureSpec,它们的测量模式都是EXACT,指定child的大小是一个精确值childDimension。
 

  childDimension为match_parent时,说明child的宽,高是铺满整个父容器DecorView的,所以child的宽,高是确定的,就是父容器的宽,高(这里为可利用区域的宽,高)。所以在这里为child生成了一个EXACT模式的测量规格,并且大小是size(父容器的宽高减去父容器的padding和child的margin,即父容器的可利用区域,同时也是child所能占用的空间)。最终计算出两个测量规格childWidthMeasureSpec 和childHeightMeasureSpec,它们的测量模式都是EXACT,指定child的大小是一个精确值size。

childDimension为wrap_content,说明child的宽,高是随自己内容的大小而变化的,但是最大不能超过父容器给的可利用区域,即size. 所以在这里为child生成了一个测量模式为AT_MOST的测量规格,大小为size,也就是说 child想要多大(宽和高)凭自己的内容大小,但是最大不能超过  size。最终生成两个测量规格childWidthMeasureSpec 和childHeightMeasureSpec,它们的测量模式都是AT_MOST,指定child的大小最大不能超过size的值。

好了,有了childWidthMeasureSpec 和childHeightMeasureSpec,就可以用这两个测量规格(约束)来测量自己了,childWidthMeasureSpec 和childHeightMeasureSpec实质就是child的父容器给child的测量规格,因为它们的生成,用到了父容器自己的测量规格,结合child的 宽高属性配置(childDimension),所以说childWidthMeasureSpec 和childHeightMeasureSpec是父容器(DecorView)给child的测量规格,一点儿也不为过。

在这里我们只分析了父容器测量规格是EXACT的情况下,如何生成child的测量规格,至于其它测量模式的分支代码,读者可以自行分析。

Ok,child得到了childWidthMeasureSpec 和childHeightMeasureSpec,接下来就可以测量自己了,然后调用

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
 

这行代码我们似曾相识,我们可以知道child.measure完后,又会调用child.onMeasure,就又会走一遍我们上面分析的onMeasure的代码,如果当前child是一个类似于DecorView的ViewGroup容器的话,有会用for循环去测量当前child的child,是一个不断嵌套递归的过程。如果child本身就是叶子节点,是一个普通的view,则for循环不会进入,直接调用onMeasure中的


      setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
       resolveSizeAndState(maxHeight, heightMeasureSpec,
       childState << MEASURED_HEIGHT_STATE_SHIFT));
  
 

就能测量出它的大小,然后递归返回child测量出的宽,高,作为child的父容器DecorView(FrameLayout extends ViewGroup)测量宽高的依据或者说参数吧。

   

3. 总结:

  所谓递归,就是要测量自身(继承于ViewGroup的容器)的大小,先得测量出child的大小,要想测出child的大小,又得先测量出child的child的大小。最终的叶子节点child的大小测量出之后,会层层返回(归的含义),逐层测量出每一个阶段的child的大小,最终递归到最外层的父容器,从而得到最初的父容器的大小,当然在这个递归过程中,各层的容器或view都得到了测量。所以,一个View(ViewGroup)的测量是一个递归的过程,同时每一层View的测量都离不开上层容器指定给它的两个测量规格:childWidthMeasureSpec 和childHeightMeasureSpec。

 

 

 

 

 

 

 

文章来源: blog.csdn.net,作者:冉航--小虾米,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/gaoxiaoweiandy/article/details/101163576

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。