android开发笔记(三十五)measure测试过程

         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。

发布了44 篇原创文章 · 获赞 27 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/gaoxiaoweiandy/article/details/101163576