measure过程

view的大三流程开始之地在performTraversals过程中,而measure是三个流程中较为复杂的过程。而measure的开始地方在performTraversals中的代码片段:

            .....
            //mStopped==true,该窗口activity处于停止状态
            //mReportNextDraw,Window上报下一次绘制
            if (!mStopped || mReportNextDraw) {
                //触摸模式发生了变化,且检测焦点的控件发生了变化
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                //1. 焦点控件发生变化
                //2. 窗口宽高测量值 != WMS计算的mWinFrame宽高
                //3. contentInsetsChanged==true,边衬区域发生变化
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                    //------开始执行测量操作--------
                     // Ask host how big it wants to be
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
             .....

可见在measure流程开始之前通过getRootMeasureSpec()获取根布局的MeasureSpec并传入performMeasure中开始measure流程。

MeasureSpec

MeasureSpec代表一个32位int值,高2位为SpecMode,低30位为SpecSize。在View中有MeasureSpec内部类定义对MeasureSpec的相关处理及常量定义。

        /** @hide */
        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {}

        /**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        // Creates a measure specification based on the supplied size and mode.
        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);
            }
        }

SpecMode有三种情况:

  • UNSPECIFIED:父布局对View没有任何约束,View可以是任何它想要的尺寸。
  • EXACTLY:父布局精确设定了View的大小,无论子布局想要多大的都将得到父布局给予的精确边界。这种情况实际上就对应的是我们在xml文件或者LayoutParams中设置的xxdp/px或者MATH_PARENT这两种情况,也就是精确的给予了布局边界。
  • AT_MOST:在确定的尺寸(父布局指定的SpecSize)内,View可以尽可能的大但不得超出SpecSize大小。这种情况对应的就是我们在xml文件或者LayoutParams中设置的WRAP_CONTENT,子View可以随着内容的增加而申请更大的尺寸,但是不能超过指定的SpecSize。

在measure过程中,对于除了DecorView外的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定的(还与View的margin和padding有关);而对于DecorView来说,其MeasureSpec由它自身的LayoutParams决定。

DecorView的MeasureSpec

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

因为DectorView的MeasureSpec只与自身LayoutParams有关。可以分为三种情况:

  • LayoutParams.MATH_PARENT:MeasureSpec.EXACTLY模式,强制DectorView大小与window大小一致
  • LayoutParams.WRAP_CONTENT:MeasureSpec.AT_MOST模式,且不可超过window大小
  • default:MeasureSpec.EXACTLY模式,指定为DectorView的lp大小

View的MeasureSpec

View的mesure过程由ViewGroup传递。ViewGroup则相对复杂一些。

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

这就与上文说到的对于除了DecorView外的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定的(还与View的margin和padding有关)
子View的MeasureSpec创建:与父容器的MeasureSpec自身的LayoutParams有关,此外还和View的margin及padding有关。

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        //子View可用空间
        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:   //父布局为EXACTLY模式,对应match_parent及dp/px
            if (  >= 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:   //父布局为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);
    }

最终可以整理出一个表格,引用《Android开发艺术探索》中的表图。


MeasureSpec

稍微整理则是:

  • 当子view指定dp/px :使用EXACTLY模式,并遵循LayoutParams大小
  • 当子view宽/高为match_parent :
    • 父布局为EXACTLY,则子view为EXACTLY,且大小为parent可用大小
    • 父布局为AT_MOST,则子view为AT_MOST,且大小为parent可用大小
  • 当子view宽/高为wrap_content:使用AT_MOST,且大小为parent可用大小

而UNSPECIFIED一般我们在开发时不会用到故而不做分析。

measure

因为view通过measure即可完成测量过程,而ViewGroup还需要遍历调用子view的measure方法。所以需要分别讨论。

View的measure

View的measure由measure方法完成,而measure方法为final类型子类不可重写。在measure方法中会调用onMeasure方法,且具体测量过程都是在该方法中完成。

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

    //1.AT_MOST、EXACTLY情况下返回MeasureSpec中的Size
    //2.UNSPECIFIED使用getSuggestedMinimumXX的返回值
    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;
    }

    //1.无背景:使用android:minWidth(mMinWidth)的值(可为0)
    //2.有背景:取背景大小或mMinWidth中的 最大值
    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
    protected int getSuggestedMinimumHeight() {
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
    }

一般我们只会使用到AT_MOST、EXACTLY模式,但是需要注意的是当为AT_MOST下默认使用的是parentSize,即相当于match_parent,所以自定义view要实现wrap_content时需要自己实现。
而使用UNSPECIFIED,则是

  • 有背景,取背景大小和mMinWidth/mMinHeight 中最大值
  • 无背景,取mMinWidth/mMinHeight
    这样子就确定了view的测量值,而view最终大小则由layout过程确定,一般情况两者一致。

ViewGroup的measure

因为ViewGroup除了完成自己的measure还需要遍历子view的measure过程。而ViewGroup是个抽象类,提供了measureChlidren的方法,而对应onMeasure则需要对应实现的ViewGroup子类去实现了。

    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);//对每个children进行measure
            }
        }
    }
    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();
        //getChildMeasureSpec可看上文的MeasureSpec解析
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

看见measureChildren时对Visibility==GONE的view不进行measure操作。对于不同ViewGroup得分析网上已经有很多分析了。这里稍微总结一下就是,

  • LinearLayout(vertical):先测量所有子View大小,根据子View总高度和自身MeasureSpec得出剩余空间去分配使用weiget的view的大小,最后测量自身大小。
  • RelativeLayout:根据依赖分别排序垂直和水平方向的view,并针对垂直和水平方向的view进行measure(一次水平一次垂直),最后测量自身大小
  • FrameLayout:测量子View,测量自身,如果自身为非精准模式而子View为match_parent则这些子view需要再次测量。

测量完是不一定能拿到View的对应测量大小的,因为系统在某些情况下可能进行多次测量测能确定宽高。所以自定义view时最好在onLayout去获取测量大小。外部获取view大小可以通过:

  • Activity/View#onWindowFocusChanged:这时view已经初始化完成,但该方法可能多次被调用(获取/失去焦点都会被调用)
  • View#post:在Android Handler原理源码浅析知道,当执行runnable时view已经初始化完成(MainLooper已经完成绘制去除了消息屏障)
  • ViewTreeObserver:通过OnGlobalLayoutListener接口回调onGlobalLayout时view树可见性已经发生变化,这时去获取view的宽高则可以(这个接口可能多次调用,回调后需取消监听)

LinearLayout Measure过程(Vertical)

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {

        final int count = getVirtualChildCount();

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);


        // See how tall everyone is. Also remember max width.
        for (int i = 0; i < count; ++i) {
            //null 或者 GONE 跳过measure
            ......

            totalWeight += lp.weight;

            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                //精准模式直接记录children高度

                // Optimization: don't bother measuring children who are only
                // laid out using excess space. These views will get measured
                // later if we have space to distribute.
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                if (useExcessSpace) {
                    // The heightMode is either UNSPECIFIED or AT_MOST, and
                    // this child is only laid out using excess space. Measure
                    // using WRAP_CONTENT so that we can find out the view's
                    // optimal height. We'll restore the original height of 0
                    // after measurement.
                    lp.height = LayoutParams.WRAP_CONTENT;
                }

                // Determine how big this child would like to be. If this or
                // previous children have given a weight, then we allow it to
                // use all available space (and we will shrink things later
                // if needed).
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                //measure子view高度
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);

                //记录children高度
                .....
            }

            .....
        }

        ......

        // Add in our padding
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        // Check against our minimum height
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
        //测量自身高度,方便计算是否还是有剩余高度
        // Reconcile our calculated size with the heightMeasureSpec
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
        // Either expand children with weight to take up available space or
        // shrink them if they extend beyond our current bounds. If we skipped
        // measurement on any children, we need to measure them now.
        int remainingExcess = heightSize - mTotalLength
                + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
        //当有还有剩余空间,则针对对应weight分配空间
        .....

        if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
            maxWidth = alternativeMaxWidth;
        }

        maxWidth += mPaddingLeft + mPaddingRight;

        // Check against our minimum width
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
        //设置自身测量大小
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);

        if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }
    }

RelativeLayout onMeasure

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mDirtyHierarchy) {
            mDirtyHierarchy = false;
            sortChildren();//根据依赖图分别按垂直和水平排序子view
        }

        ...
        //根据水平方向依赖关系,measure子view
        View[] views = mSortedHorizontalChildren;
        int count = views.length;

        for (int i = 0; i < count; i++) {
            View child = views[i];
            if (child.getVisibility() != GONE) {
                LayoutParams params = (LayoutParams) child.getLayoutParams();
                int[] rules = params.getRules(layoutDirection);

                applyHorizontalSizeRules(params, myWidth, rules);
                measureChildHorizontal(child, params, myWidth, myHeight);

                if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
                    offsetHorizontalAxis = true;
                }
            }
        }
        //根据垂直方向依赖关系,measure子view
        views = mSortedVerticalChildren;
        count = views.length;
        final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;

        for (int i = 0; i < count; i++) {
            final View child = views[i];
            if (child.getVisibility() != GONE) {
                final LayoutParams params = (LayoutParams) child.getLayoutParams();

                applyVerticalSizeRules(params, myHeight, child.getBaseline());
                measureChild(child, params, myWidth, myHeight);
                if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
                    offsetVerticalAxis = true;
                }

                ....
            }
        }

        //测量自身
        ....

        setMeasuredDimension(width, height);
    }

FrameLayout onMeasure

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        //是否需要测量match_parent的子view,当为精准模式不需要测量match_parent的子view
        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) {
                //测量ziview
                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());
                //将match_parent子view添加进列表,需要再次测量
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

        .....
        //测量自身大小
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));

        count = mMatchParentChildren.size();
        //自身非精准模式,而子view为match_parent需要重新测量这些view
        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);
            }
        }
    }

关于ConstraintLayout的measure解析,日后补上

参考:

  • 《Android开发艺术探索》
  • View绘制流程及源码解析(二)——onMeasure()流程分析

你可能感兴趣的:(measure过程)