美文网首页
measure过程

measure过程

作者: KIDNG_LGJ | 来源:发表于2018-09-18 17:13 被阅读0次

    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解析,日后补上

    参考:

    相关文章

      网友评论

          本文标题:measure过程

          本文链接:https://www.haomeiwen.com/subject/ipolnftx.html