measure過程

view的大三流程開始之地在performTraversals過程中,而measure是三個流程中較為復(fù)雜的過程。而measure的開始地方在performTraversals中的代碼片段:

            .....
            //mStopped==true,該窗口activity處于停止?fàn)顟B(tài)
            //mReportNextDraw,Window上報下一次繪制
            if (!mStopped || mReportNextDraw) {
                //觸摸模式發(fā)生了變化,且檢測焦點(diǎn)的控件發(fā)生了變化
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                //1. 焦點(diǎn)控件發(fā)生變化
                //2. 窗口寬高測量值 != WMS計(jì)算的mWinFrame寬高
                //3. contentInsetsChanged==true,邊襯區(qū)域發(fā)生變化
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                    //------開始執(zhí)行測量操作--------
                     // 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內(nèi)部類定義對MeasureSpec的相關(guān)處理及常量定義。

        /** @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:父布局精確設(shè)定了View的大小,無論子布局想要多大的都將得到父布局給予的精確邊界。這種情況實(shí)際上就對應(yīng)的是我們在xml文件或者LayoutParams中設(shè)置的xxdp/px或者M(jìn)ATH_PARENT這兩種情況,也就是精確的給予了布局邊界。
  • AT_MOST:在確定的尺寸(父布局指定的SpecSize)內(nèi),View可以盡可能的大但不得超出SpecSize大小。這種情況對應(yīng)的就是我們在xml文件或者LayoutParams中設(shè)置的WRAP_CONTENT,子View可以隨著內(nèi)容的增加而申請更大的尺寸,但是不能超過指定的SpecSize。

在measure過程中,對于除了DecorView外的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定的(還與View的margin和padding有關(guān));而對于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;
    }

因?yàn)镈ectorView的MeasureSpec只與自身LayoutParams有關(guān)。可以分為三種情況:

  • LayoutParams.MATH_PARENT:MeasureSpec.EXACTLY模式,強(qiáng)制DectorView大小與window大小一致
  • LayoutParams.WRAP_CONTENT:MeasureSpec.AT_MOST模式,且不可超過window大小
  • default:MeasureSpec.EXACTLY模式,指定為DectorView的lp大小

View的MeasureSpec

View的mesure過程由ViewGroup傳遞。ViewGroup則相對復(fù)雜一些。

    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有關(guān))
子View的MeasureSpec創(chuàng)建:與父容器的MeasureSpec自身的LayoutParams有關(guān),此外還和View的margin及padding有關(guān)。

    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模式,對應(yīng)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開發(fā)藝術(shù)探索》中的表圖。


MeasureSpec

稍微整理則是:

  • 當(dāng)子view指定dp/px :使用EXACTLY模式,并遵循LayoutParams大小
  • 當(dāng)子view寬/高為match_parent :
    • 父布局為EXACTLY,則子view為EXACTLY,且大小為parent可用大小
    • 父布局為AT_MOST,則子view為AT_MOST,且大小為parent可用大小
  • 當(dāng)子view寬/高為wrap_content:使用AT_MOST,且大小為parent可用大小

而UNSPECIFIED一般我們在開發(fā)時不會用到故而不做分析。

measure

因?yàn)関iew通過measure即可完成測量過程,而ViewGroup還需要遍歷調(diào)用子view的measure方法。所以需要分別討論。

View的measure

View的measure由measure方法完成,而measure方法為final類型子類不可重寫。在measure方法中會調(diào)用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模式,但是需要注意的是當(dāng)為AT_MOST下默認(rèn)使用的是parentSize,即相當(dāng)于match_parent,所以自定義view要實(shí)現(xiàn)wrap_content時需要自己實(shí)現(xiàn)。
而使用UNSPECIFIED,則是

  • 有背景,取背景大小和mMinWidth/mMinHeight 中最大值
  • 無背景,取mMinWidth/mMinHeight
    這樣子就確定了view的測量值,而view最終大小則由layout過程確定,一般情況兩者一致。

ViewGroup的measure

因?yàn)閂iewGroup除了完成自己的measure還需要遍歷子view的measure過程。而ViewGroup是個抽象類,提供了measureChlidren的方法,而對應(yīng)onMeasure則需要對應(yīng)實(shí)現(xiàn)的ViewGroup子類去實(shí)現(xiàn)了。

    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進(jìn)行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不進(jìn)行measure操作。對于不同ViewGroup得分析網(wǎng)上已經(jīng)有很多分析了。這里稍微總結(jié)一下就是,

  • LinearLayout(vertical):先測量所有子View大小,根據(jù)子View總高度和自身MeasureSpec得出剩余空間去分配使用weiget的view的大小,最后測量自身大小。
  • RelativeLayout:根據(jù)依賴分別排序垂直和水平方向的view,并針對垂直和水平方向的view進(jìn)行measure(一次水平一次垂直),最后測量自身大小
  • FrameLayout:測量子View,測量自身,如果自身為非精準(zhǔn)模式而子View為match_parent則這些子view需要再次測量。

測量完是不一定能拿到View的對應(yīng)測量大小的,因?yàn)橄到y(tǒng)在某些情況下可能進(jìn)行多次測量測能確定寬高。所以自定義view時最好在onLayout去獲取測量大小。外部獲取view大小可以通過:

  • Activity/View#onWindowFocusChanged:這時view已經(jīng)初始化完成,但該方法可能多次被調(diào)用(獲取/失去焦點(diǎn)都會被調(diào)用)
  • View#post:在Android Handler原理源碼淺析知道,當(dāng)執(zhí)行runnable時view已經(jīng)初始化完成(MainLooper已經(jīng)完成繪制去除了消息屏障)
  • ViewTreeObserver:通過OnGlobalLayoutListener接口回調(diào)onGlobalLayout時view樹可見性已經(jīng)發(fā)生變化,這時去獲取view的寬高則可以(這個接口可能多次調(diào)用,回調(diào)后需取消監(jiān)聽)

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) {
                //精準(zhǔn)模式直接記錄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());
        //測量自身高度,方便計(jì)算是否還是有剩余高度
        // 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);
        //當(dāng)有還有剩余空間,則針對對應(yīng)weight分配空間
        .....

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

        maxWidth += mPaddingLeft + mPaddingRight;

        // Check against our minimum width
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
        //設(shè)置自身測量大小
        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();//根據(jù)依賴圖分別按垂直和水平排序子view
        }

        ...
        //根據(jù)水平方向依賴關(guān)系,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;
                }
            }
        }
        //根據(jù)垂直方向依賴關(guān)系,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,當(dāng)為精準(zhǔn)模式不需要測量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添加進(jìn)列表,需要再次測量
                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();
        //自身非精準(zhǔn)模式,而子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);
            }
        }
    }

關(guān)于ConstraintLayout的measure解析,日后補(bǔ)上

參考:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容