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函数

代码如下:


  
  1. /**
  2. * <p>
  3. * This is called to find out how big a view should be. The parent
  4. * supplies constraint information in the width and height parameters.
  5. * </p>
  6. *
  7. * <p>
  8. * The actual measurement work of a view is performed in
  9. * {@link #onMeasure(int, int)}, called by this method. Therefore, only
  10. * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
  11. * </p>
  12. *
  13. *
  14. * @param widthMeasureSpec Horizontal space requirements as imposed by the
  15. * parent
  16. * @param heightMeasureSpec Vertical space requirements as imposed by the
  17. * parent
  18. *
  19. * @see #onMeasure(int, int)
  20. */
  21. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  22. boolean optical = isLayoutModeOptical(this);
  23. if (optical != isLayoutModeOptical(mParent)) {
  24. Insets insets = getOpticalInsets();
  25. int oWidth = insets.left + insets.right;
  26. int oHeight = insets.top + insets.bottom;
  27. widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
  28. heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
  29. }
  30. // Suppress sign extension for the low bytes
  31. long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
  32. if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
  33. final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
  34. // Optimize layout by avoiding an extra EXACTLY pass when the view is
  35. // already measured as the correct size. In API 23 and below, this
  36. // extra pass is required to make LinearLayout re-distribute weight.
  37. final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
  38. || heightMeasureSpec != mOldHeightMeasureSpec;
  39. final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
  40. && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
  41. final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
  42. && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
  43. final boolean needsLayout = specChanged
  44. && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
  45. if (forceLayout || needsLayout) {
  46. // first clears the measured dimension flag
  47. mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
  48. resolveRtlPropertiesIfNeeded();
  49. int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
  50. if (cacheIndex < 0 || sIgnoreMeasureCache) {
  51. // measure ourselves, this should set the measured dimension flag back
  52. onMeasure(widthMeasureSpec, heightMeasureSpec);
  53. mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
  54. } else {
  55. long value = mMeasureCache.valueAt(cacheIndex);
  56. // Casting a long to int drops the high 32 bits, no mask needed
  57. setMeasuredDimensionRaw((int) (value >> 32), (int) value);
  58. mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
  59. }
  60. // flag not set, setMeasuredDimension() was not invoked, we raise
  61. // an exception to warn the developer
  62. if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
  63. throw new IllegalStateException("View with id " + getId() + ": "
  64. + getClass().getName() + "#onMeasure() did not set the"
  65. + " measured dimension by calling"
  66. + " setMeasuredDimension()");
  67. }
  68. mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
  69. }
  70. mOldWidthMeasureSpec = widthMeasureSpec;
  71. mOldHeightMeasureSpec = heightMeasureSpec;
  72. mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
  73. (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
  74. }

 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


  
  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  2. setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
  3. getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  4. }

   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的成员变量里,代码如下:


  
  1. private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
  2. mMeasuredWidth = measuredWidth;
  3. mMeasuredHeight = measuredHeight;
  4. mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
  5. }

      2.2.2 onMeasure深入分析

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


  
  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. int count = getChildCount();
  4. final boolean measureMatchParentChildren =
  5. MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
  6. MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
  7. mMatchParentChildren.clear();
  8. int maxHeight = 0;
  9. int maxWidth = 0;
  10. int childState = 0;
  11. for (int i = 0; i < count; i++) {
  12. final View child = getChildAt(i);
  13. if (mMeasureAllChildren || child.getVisibility() != GONE) {
  14. measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
  15. final LayoutParams lp = (LayoutParams) child.getLayoutParams();
  16. maxWidth = Math.max(maxWidth,
  17. child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
  18. maxHeight = Math.max(maxHeight,
  19. child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
  20. childState = combineMeasuredStates(childState, child.getMeasuredState());
  21. if (measureMatchParentChildren) {
  22. if (lp.width == LayoutParams.MATCH_PARENT ||
  23. lp.height == LayoutParams.MATCH_PARENT) {
  24. mMatchParentChildren.add(child);
  25. }
  26. }
  27. }
  28. }
  29. // Account for padding too
  30. maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
  31. maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
  32. // Check against our minimum height and width
  33. maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
  34. maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
  35. // Check against our foreground's minimum height and width
  36. final Drawable drawable = getForeground();
  37. if (drawable != null) {
  38. maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
  39. maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
  40. }
  41. setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
  42. resolveSizeAndState(maxHeight, heightMeasureSpec,
  43. childState << MEASURED_HEIGHT_STATE_SHIFT));
  44. count = mMatchParentChildren.size();
  45. if (count > 1) {
  46. for (int i = 0; i < count; i++) {
  47. final View child = mMatchParentChildren.get(i);
  48. final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
  49. final int childWidthMeasureSpec;
  50. if (lp.width == LayoutParams.MATCH_PARENT) {
  51. final int width = Math.max(0, getMeasuredWidth()
  52. - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
  53. - lp.leftMargin - lp.rightMargin);
  54. childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
  55. width, MeasureSpec.EXACTLY);
  56. } else {
  57. childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
  58. getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
  59. lp.leftMargin + lp.rightMargin,
  60. lp.width);
  61. }
  62. final int childHeightMeasureSpec;
  63. if (lp.height == LayoutParams.MATCH_PARENT) {
  64. final int height = Math.max(0, getMeasuredHeight()
  65. - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
  66. - lp.topMargin - lp.bottomMargin);
  67. childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
  68. height, MeasureSpec.EXACTLY);
  69. } else {
  70. childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
  71. getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
  72. lp.topMargin + lp.bottomMargin,
  73. lp.height);
  74. }
  75. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  76. }
  77. }
  78. }

        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函数中的这段代码:


  
  1. setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
  2. resolveSizeAndState(maxHeight, heightMeasureSpec,
  3. childState << MEASURED_HEIGHT_STATE_SHIFT));

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

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


  
  1. /**
  2. * Utility to reconcile a desired size and state, with constraints imposed
  3. * by a MeasureSpec. Will take the desired size, unless a different size
  4. * is imposed by the constraints. The returned value is a compound integer,
  5. * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
  6. * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the
  7. * resulting size is smaller than the size the view wants to be.
  8. *
  9. * @param size How big the view wants to be.
  10. * @param measureSpec Constraints imposed by the parent.
  11. * @param childMeasuredState Size information bit mask for the view's
  12. * children.
  13. * @return Size information bit mask as defined by
  14. * {@link #MEASURED_SIZE_MASK} and
  15. * {@link #MEASURED_STATE_TOO_SMALL}.
  16. */
  17. public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
  18. final int specMode = MeasureSpec.getMode(measureSpec);
  19. final int specSize = MeasureSpec.getSize(measureSpec);
  20. final int result;
  21. switch (specMode) {
  22. case MeasureSpec.AT_MOST:
  23. if (specSize < size) {
  24. result = specSize | MEASURED_STATE_TOO_SMALL;
  25. } else {
  26. result = size;
  27. }
  28. break;
  29. case MeasureSpec.EXACTLY:
  30. result = specSize;
  31. break;
  32. case MeasureSpec.UNSPECIFIED:
  33. default:
  34. result = size;
  35. }
  36. return result | (childMeasuredState & MEASURED_STATE_MASK);
  37. }

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

  
  1.     case MeasureSpec.EXACTLY:
  2.                 result = specSize;
  3.                 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的代码如下:


  
  1. for (int i = 0; i < count; i++) {
  2. final View child = getChildAt(i);
  3. if (mMeasureAllChildren || child.getVisibility() != GONE) {
  4. measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
  5. final LayoutParams lp = (LayoutParams) child.getLayoutParams();
  6. maxWidth = Math.max(maxWidth,
  7. child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
  8. maxHeight = Math.max(maxHeight,
  9. child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
  10. childState = combineMeasuredStates(childState, child.getMeasuredState());
  11. if (measureMatchParentChildren) {
  12. if (lp.width == LayoutParams.MATCH_PARENT ||
  13. lp.height == LayoutParams.MATCH_PARENT) {
  14. mMatchParentChildren.add(child);
  15. }
  16. }
  17. }
  18. }

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

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

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

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


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

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


  
  1. setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
  2. resolveSizeAndState(maxHeight, heightMeasureSpec,
  3. childState << MEASURED_HEIGHT_STATE_SHIFT));


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

 

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

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


  
  1. /**
  2. * Ask one of the children of this view to measure itself, taking into
  3. * account both the MeasureSpec requirements for this view and its padding
  4. * and margins. The child must have MarginLayoutParams The heavy lifting is
  5. * done in getChildMeasureSpec.
  6. *
  7. * @param child The child to measure
  8. * @param parentWidthMeasureSpec The width requirements for this view
  9. * @param widthUsed Extra space that has been used up by the parent
  10. * horizontally (possibly by other children of the parent)
  11. * @param parentHeightMeasureSpec The height requirements for this view
  12. * @param heightUsed Extra space that has been used up by the parent
  13. * vertically (possibly by other children of the parent)
  14. */
  15. protected void measureChildWithMargins(View child,
  16. int parentWidthMeasureSpec, int widthUsed,
  17. int parentHeightMeasureSpec, int heightUsed) {
  18. final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
  19. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
  20. mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
  21. + widthUsed, lp.width);
  22. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
  23. mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
  24. + heightUsed, lp.height);
  25. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  26. }

 

     (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的生成。

 


  
  1. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
  2. mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
  3. + widthUsed, lp.width);
  4. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
  5. mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
  6. + heightUsed, lp.height);

我们可以看到getChildMeasureSpec函数,用到了parentWidthMeasureSpec和parentHeightMeasureSpec,可见子child的测量规格的生成离不开它的父容器的测量规格。我们深入getChildMeasureSpec:


  
  1. /**
  2. * Does the hard part of measureChildren: figuring out the MeasureSpec to
  3. * pass to a particular child. This method figures out the right MeasureSpec
  4. * for one dimension (height or width) of one child view.
  5. *
  6. * The goal is to combine information from our MeasureSpec with the
  7. * LayoutParams of the child to get the best possible results. For example,
  8. * if the this view knows its size (because its MeasureSpec has a mode of
  9. * EXACTLY), and the child has indicated in its LayoutParams that it wants
  10. * to be the same size as the parent, the parent should ask the child to
  11. * layout given an exact size.
  12. *
  13. * @param spec The requirements for this view
  14. * @param padding The padding of this view for the current dimension and
  15. * margins, if applicable
  16. * @param childDimension How big the child wants to be in the current
  17. * dimension
  18. * @return a MeasureSpec integer for the child
  19. */
  20. public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
  21. int specMode = MeasureSpec.getMode(spec);
  22. int specSize = MeasureSpec.getSize(spec);
  23. int size = Math.max(0, specSize - padding);
  24. int resultSize = 0;
  25. int resultMode = 0;
  26. switch (specMode) {
  27. // Parent has imposed an exact size on us
  28. case MeasureSpec.EXACTLY:
  29. if (childDimension >= 0) {
  30. resultSize = childDimension;
  31. resultMode = MeasureSpec.EXACTLY;
  32. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  33. // Child wants to be our size. So be it.
  34. resultSize = size;
  35. resultMode = MeasureSpec.EXACTLY;
  36. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  37. // Child wants to determine its own size. It can't be
  38. // bigger than us.
  39. resultSize = size;
  40. resultMode = MeasureSpec.AT_MOST;
  41. }
  42. break;
  43. // Parent has imposed a maximum size on us
  44. case MeasureSpec.AT_MOST:
  45. if (childDimension >= 0) {
  46. // Child wants a specific size... so be it
  47. resultSize = childDimension;
  48. resultMode = MeasureSpec.EXACTLY;
  49. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  50. // Child wants to be our size, but our size is not fixed.
  51. // Constrain child to not be bigger than us.
  52. resultSize = size;
  53. resultMode = MeasureSpec.AT_MOST;
  54. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  55. // Child wants to determine its own size. It can't be
  56. // bigger than us.
  57. resultSize = size;
  58. resultMode = MeasureSpec.AT_MOST;
  59. }
  60. break;
  61. // Parent asked to see how big we want to be
  62. case MeasureSpec.UNSPECIFIED:
  63. if (childDimension >= 0) {
  64. // Child wants a specific size... let him have it
  65. resultSize = childDimension;
  66. resultMode = MeasureSpec.EXACTLY;
  67. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  68. // Child wants to be our size... find out how big it should
  69. // be
  70. resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
  71. resultMode = MeasureSpec.UNSPECIFIED;
  72. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  73. // Child wants to determine its own size.... find out how
  74. // big it should be
  75. resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
  76. resultMode = MeasureSpec.UNSPECIFIED;
  77. }
  78. break;
  79. }
  80. //noinspection ResourceType
  81. return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
  82. }

目前对于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中的


  
  1. setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
  2. resolveSizeAndState(maxHeight, heightMeasureSpec,
  3. 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个月内不可修改。