Android學習筆記---深入理解View#03

上一篇的結(jié)尾中,我們發(fā)現(xiàn)了View的繪制發(fā)生在ViewRootImplperformTraversals()中.而且在其中先后調(diào)用了performMeasure(),performLayout(),performDraw().
如此一來,我們又有了新的獵物了.就像美食一樣,好吃的東西一定要仔細地品嘗.在上主菜之前,我們先來點開胃菜.我們先來了解一下Android是怎樣繪制View的.(官方的文檔How Android Draws Views)

開胃菜(關(guān)于View繪制時需要知道的常識)

Activity接受焦點時,就會被要求繪制其布局.雖然Android Framework會處理這個過程,但Activity必須提供整個布局層級的根節(jié)點,因為需要知道從哪開始繪制.

Activity的整個布局被轉(zhuǎn)換成了一棵樹,繪制整個布局就相當于了遍歷整顆樹并把每個節(jié)點的View繪制出來.相應地,ViewGroup負責要求它的每一個child進行繪制,而View則負責繪制自己.由于樹的遍歷是有序的,所以父View繪制之前會先繪制其子View,而兄弟節(jié)點會按照在樹中出現(xiàn)的順序進行繪制.
繪制布局需要進行兩個傳遞過程(pass process):分別是測量時的傳遞(measure pass)布局時的傳遞(layout pass).這里所說的傳遞指的是在view tree的各個節(jié)點之間的傳遞.

  1. measure passmeasure(int,int)中實現(xiàn),而且它是一個從上到下的傳遞.在view tree中每個View節(jié)點都將它的尺寸規(guī)格向下傳遞給它的孩子,在整個傳遞過程結(jié)束時,每個節(jié)點都應該擁有了自己的測量值(尺寸大小).
  2. layout passlayout(int,int,int,int)中發(fā)生,它同樣也是一個從上到下的傳遞.在傳遞過程中每個parent都需要根據(jù)在measure pass時得到的測量值在布局中放置它的所有children.

下面貼上一張普通的view tree的圖.

measure()函數(shù)中,官方定下了一些規(guī)則,在函數(shù)執(zhí)行完畢返回前必須要滿足下面的條件:

  1. View(以及其后代節(jié)點)的getMeasuredWidth()(即mMeasuredWidth的值)getMeasuredHeight()(即mMeasuredHeight的值)的值必須已經(jīng)設置.從函數(shù)名已經(jīng)知道函數(shù)獲取的是已經(jīng)測量的寬高值,measure()函數(shù)結(jié)束就表明測量結(jié)束了,這一條規(guī)則理所當然.
  2. View測量后的寬高必須符合其父View所規(guī)定的大小.這一條規(guī)則可以保證當measure pass結(jié)束時,所有的parents能接受其所有children的測量值.這也很好理解,子View的大小總不能比其父布局還大吧,否則就沒有意義了.

一個為parent的View可能會不止一次地對其children調(diào)用measure().因為如果parent使用未指定的尺寸測量它的每一個child得到各個child想要的大小,但如果所有children的(未加限制的)測量值的總和太大或太小,那就需要parent再次調(diào)用measure()重新測量,但這次的測量設置了相應的規(guī)則.(舉個比喻,就像孩子們在分配糖果時,大家都對所分配的糖果不滿意時,父母就會干涉并重新分配)

我要吃神戶牛柳(深入measure過程)

吃過開胃菜后,再來品嘗我們的主菜就會更加的美味.美味的食物通常都有獨特的吃法,比如使用特定的餐具.我們的第一道菜(measure)就是神戶牛柳,我們需要準備刀叉來用餐.那先準備一下我們的餐具吧.(與measure過程密切相關(guān)的兩個類).

刀 (ViewGroup.LayoutParams)

先來說明一下ViewGroup.LayoutParams是干什么用的.View通過ViewGroup.LayoutParams來告訴它的parent它在布局中想被放在什么位置和想占多大.而基本的ViewGroup.LayoutParams只能表達View想占多寬和多高,可以通過下面的其中一種方式表達:

  • 一個確切的數(shù)值大小
  • MATCH_PARENT,表達View想要和它的parent一樣大(去掉View的內(nèi)邊距)
  • WRAP_CONTENT,表達View只想要能將它的內(nèi)容包裹的大小(加上View的內(nèi)邊距)

ViewGroup.LayoutParams只能表達View大小,但ViewGroup的子類的LayoutParams能表達View的位置.

叉 (MeasureSpec)

看過我的自定義View#02文章的同學可能會對MeasureSpec有所了解.MeasureSpecparent用來限制child的大小,在measure()的過程中,它作為參數(shù),從view tree的根節(jié)點往下傳遞到它的子節(jié)點和其后代.它有下列3種模式:

  • UNSPECIFIED, 這種模式表明parent對它的child的大小沒有限制,child可以告訴parent它自己所希望的尺寸.
  • EXACTLY, 這種模式表明parentchild設置了一個確切的值,child必須使用這個值,并且需要保證child的后代節(jié)點都要符合這個值的設置
  • AT_MOST, 這種模式表明parentchild設置了一個最大值,child可以是它想要的任何值,但child以及它的后代節(jié)點的尺寸大小都必須保證在這個最大值內(nèi).

既然MeasureSpec有相應的模式來限制View的尺寸,那用什么來表示限制尺寸的大小呢.MeasureSpec采用了一個32位的int值來代表模式和大小,高2位表示模式,低30位表示大小.

有了我們的餐具后,我們可以終于可以開動了.我們先從ViewRootImpl.performTraversals()中調(diào)用performMeasure()的地方開始,下面是該部分的代碼:

.......
        if (!mStopped || mReportNextDraw) {
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
                    // 標注 1
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                    if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed!  mWidth="
                            + mWidth + " measuredWidth=" + host.getMeasuredWidth()
                            + " mHeight=" + mHeight
                            + " measuredHeight=" + host.getMeasuredHeight()
                            + " coveredInsetsChanged=" + contentInsetsChanged);

                     // Ask host how big it wants to be
                     // 標注 2
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Implementation of weights from WindowManager.LayoutParams
                    // We just grow the dimensions as needed and re-measure if
                    // needs be
                    // 標注 3
                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;
                    
                    if (lp.horizontalWeight > 0.0f) {
                        width += (int) ((mWidth - width) * lp.horizontalWeight);
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    if (lp.verticalWeight > 0.0f) {
                        height += (int) ((mHeight - height) * lp.verticalWeight);
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    // 標注 4
                    if (measureAgain) {
                        if (DEBUG_LAYOUT) Log.v(TAG,
                                "And hey let's measure once more: width=" + width
                                + " height=" + height);
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }

                    layoutRequested = true;
                }
            }
.......

上面的代碼我做了4個標注,我們一個一個來看,先看標注1的代碼.

// 標注 1
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

其中這里的mHeightmWidth分別是窗口(Activity)的寬高,lp為窗口(Window)的布局參數(shù).childWidthMeasureSpecchildHeightMeasureSpec這兩個變量其實從標注2的代碼就很容易的看出它們是作為參數(shù)傳進performMeasure(int,int)的.那我們就看看它們代表的是什么意思.

由于它們是從getRootMeasureSpec(int,int)獲取的,從函數(shù)名可以看出這個函數(shù)是用來獲取Root節(jié)點的MeasureSpec的(就是根節(jié)點在測量時給它的孩子節(jié)點所定下的尺寸大小的限制).但我們還是要看看這個函數(shù)的代碼:

    /**
     * Figures out the measure spec for the root view in a window based on it's
     * layout params.
     *
     * @param windowSize
     *            The available width or height of the window
     *
     * @param rootDimension
     *            The layout params for one dimension (width or height) of the
     *            window.
     *
     * @return The measure spec to use to measure the root view.
     */
    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.
            // 如果布局參數(shù)要求MATCH_PARENT,那么就設置為窗口的大小,模式為EXACTLY,因為窗口(Activity)的大小固定
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            //如果布局參數(shù)為WRAP_CONTENT,就設置為AT_MOST模式,最大值為窗口大小
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            // 如果布局的參數(shù)為一個確切的值,那我們就讓root view為該值,模式為EXACTLY
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

getRootMeasureSpec(int,int)的兩個參數(shù)分別代表窗口的大小(windowSize)和窗口的布局參數(shù)的大小(rootDimension).官方的注釋(我也做了相應的注釋)已經(jīng)寫得很清楚了,這個函數(shù)通過window的布局參數(shù)來決定root viewMeasureSpec.
經(jīng)過標注1的代碼,我們獲取到了root tree的根節(jié)點的MeasureSpec,這樣就可以從樹的根節(jié)點開始進行測量傳遞的過程了(在開胃菜中提到的measure pass).在對標注2這個最主要的代碼部分進行分析前,我們先來分析后面的標注3標注4的代碼.(好東西肯定要留到最后,反正我是這樣想的)

                    // Implementation of weights from WindowManager.LayoutParams
                    // We just grow the dimensions as needed and re-measure if
                    // needs be
                    // 標注 3
                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;
                     
                    if (lp.horizontalWeight > 0.0f) {
                        width += (int) ((mWidth - width) * lp.horizontalWeight);
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    if (lp.verticalWeight > 0.0f) {
                        height += (int) ((mHeight - height) * lp.verticalWeight);
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    // 標注 4
                    if (measureAgain) {
                        if (DEBUG_LAYOUT) Log.v(TAG,
                                "And hey let's measure once more: width=" + width
                                + " height=" + height);
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }

                    layoutRequested = true;

標注3首先獲取到了測量后root view的寬高值,然后分別判斷lp.horizontalWeightlp.verticalWeight的值是否大于0(即我們的布局參數(shù)在橫向或縱向的weight被設置時,可以理解為在xml布局文件里設置了layout_weight屬性).若設置了其中一個方向上的weight,那么標注4的代碼都會執(zhí)行,我們需要再調(diào)用一次performMeasure()進行測量(measure pass),但這次采用的是新的參數(shù),把布局參數(shù)的weight考慮進去.

可能有同學會問,為什么第一次調(diào)用performMeasure()前不把weight考慮進去,測量完一次后才考慮這不讓前面的工作都白費了嗎?我也有相同的疑問,目前我還沒找到一個準確的答案,但google這樣寫一定有它的道理,我在這里分享一下我的想法吧(不一定是正確的,如果錯了希望大家能指正).

我是這樣想的:第一次調(diào)用performMeasure()的時候并不知道weight是否設置了,因為我們通常設置layout_weight屬性都是在子View中設置的,在子View測量完畢前,父布局并不知道它的所有子View的weight屬性,而父布局的測量發(fā)生在子View測量結(jié)束后,所以我們可能需要進行兩次的測量傳遞過程(measure pass).

好的,清楚了我們標注3,標注4的代碼后,我們可以迎接我們的主角performMeasure()了,下面就是performMeasure()的代碼.

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

原來代碼中調(diào)用的是mView.measure(int,int),即調(diào)用了root viewmeasure(),既然如此,我們來看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);

        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {

            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -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
    }

注釋中也說了,真正的測量工作是發(fā)生在onMeasure(int,int)函數(shù)中的,并且說明了View的子類可以并必須重寫onMeasure()來測量我們的View.這里的必須并不代表我們在自定義VIew的時候一定要重寫onMeasure(),因為onMeasure()已經(jīng)在View中實現(xiàn)了,在不重寫的情況下會調(diào)用默認的實現(xiàn).

既然注釋中給我們指明了方向,那我們就來看看這個onMeasure().

神戶牛的精華(onMeasure)

onMeasure()可以說是整個measure pass的核心部分,就像是神戶牛的精華一樣.那現(xiàn)在我們就來感受一下這神戶牛的精華所帶來的美味.
由于在view tree上不可能每個節(jié)點都是View節(jié)點(這里是葉子節(jié)點的意思),就像在"開胃菜"中給大家展現(xiàn)的圖一樣,在view tree中也會有ViewGroup節(jié)點,像FrameLayout,LinearLayout,RelativeLayout...,這些ViewGroup節(jié)點都相應的實現(xiàn)了自己的onMeasure().那么這就說明了ViewGroup.onMeasure()View.onMeasure()并不一樣.既然測量傳遞的過程(measure pass)是從根節(jié)點開始的,那我們也從ViewGrouponMeasure()開始.這里我們使用的是FrameLayout的代碼(其他的ViewGroup大家可以自己試著去分析).

提示:下面的代碼可以先跳過,因為在后面會再提到.

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 第1部分
        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);
                    }
                }
            }
        }
        
        // 第2部分
        // 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));
        
        //第3部分
        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);
            }
        }
    }

Part 1

雖然代碼不算很長,但為了方便,我們還是將它分為3個部分來分析吧(上面代碼注釋中所劃分的).先來第1部分的代碼:

        // 第1部分
        int count = getChildCount();
        // 編號1. 用于判斷是否需要對布局參數(shù)為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) {
                // 編號2. 對每個子View進行測量
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                // 編號3. 得到所有子View中最大的寬度(加上子View的外邊距)
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                // 編號4. 得到所有子View中最大的高度(加上子View的外邊距)        
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                // 編號5. 得到子View的MeasureState
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                // 編號6. 將布局參數(shù)為MATCH_PARENT的子View加入到`mMatchParentChildren`集合中
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

為了后面的分析方便,我在上面的代碼注釋中都給相應的語句進行了編號,我們先來分析簡單的.
編號1measureMatchParentChildren是用于判斷FrameLayout是否需要對布局參數(shù)為MATCH_PARENT的子View進行第二次測量.這里我們等分析過編號2的代碼后再對這個變量進行解析(這里可以先留個疑問).
編號3,編號4的代碼就是為了得到FrameLayout所有孩子中測量后的最大寬高(加上邊距),因為FrameLayout需要按照它的孩子中尺寸最大的寬高進行測量.
編號5的代碼就是為了得到子View的MeasuredState,這個對我們來說是個新的概念.既然這樣,我們就來看看child.getMeasuredState()這個在View類下的函數(shù)是個怎樣的函數(shù).

    /**
     * Return only the state bits of {@link #getMeasuredWidthAndState()}
     * and {@link #getMeasuredHeightAndState()}, combined into one integer.
     * The width component is in the regular bits {@link #MEASURED_STATE_MASK}
     * and the height component is at the shifted bits
     * {@link #MEASURED_HEIGHT_STATE_SHIFT}>>{@link #MEASURED_STATE_MASK}.
     */
     // 將寬高的狀態(tài)位結(jié)合成在一個32位的int值并返回
     // 寬度的狀態(tài)位在常規(guī)的位置
     // 高度的狀態(tài)位在偏移后的位置
    public final int getMeasuredState() {
        return (mMeasuredWidth&MEASURED_STATE_MASK)
                | ((mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT)
                        & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
    }
    // 用于使高度的狀態(tài)位偏移的位數(shù)
    public static final int MEASURED_HEIGHT_STATE_SHIFT = 16;

我把用到的變量也貼在了上面的代碼中.首先我們來了解一下什么是"寬高的狀態(tài)位".我們知道mMeasuredHeightmMeasuredWidth都是32位的int值,但這個值并不是一個表示寬高的實際大小的值,而是一個由寬高的狀態(tài)和實際大小所組合的值.這里的高8位就表示狀態(tài)(STATE),而低24位表示的是實際的尺寸大小(SIZE),這個信息可以從它們相應的掩碼看出.

    // 用于得出寬高的狀態(tài)位的掩碼
    public static final int MEASURED_STATE_MASK = 0xff000000;
    // 用于得出寬高的尺寸位的掩碼
    public static final int MEASURED_SIZE_MASK = 0x00ffffff;

這就解析了為什么我們的getMeasuredHeight()函數(shù)返回的是mMeasuredHeight & MEASURED_SIZE_MASK.而getMeasuredHeightAndState()返回的是mMeasuredHeight.相應的關(guān)于寬度的函數(shù)也是一個道理.

    public final int getMeasuredHeight() {
        return mMeasuredHeight & MEASURED_SIZE_MASK;
    }
    public final int getMeasuredHeightAndState() {
        return mMeasuredHeight;
    }

現(xiàn)在我們再來看getMeasuredState()是怎樣將寬高的狀態(tài)位組合在一個int值中的.首先mMeasuredWidth & MEASURED_STATE_MASK得到了寬度的狀態(tài)位,保存在高8位.然后通過(mMeasuredHeight >> MEASURED_HEIGHT_STATE_SHIFT)(MEASURED_STATE_MASK >> MEASURED_HEIGHT_STATE_SHIFT)將高度和狀態(tài)掩碼都右移了16位,現(xiàn)在高度的狀態(tài)位在第8到第15位上,而MEASURED_STATE_MASK變成了0x0000ff00,接著將兩個移位后的數(shù)進行按位相與(&)得到了高度的狀態(tài)位,保存在8-15位上.最后將處理后寬度和高度按位相或(|)得到一個保存了寬度和高度的狀態(tài)位的int值.如下圖.

    /**
     * Merge two states as returned by {@link #getMeasuredState()}.
     * @param curState The current state as returned from a view or the result
     * of combining multiple views.
     * @param newState The new view state to combine.
     * @return Returns a new integer reflecting the combination of the two
     * states.
     */
    public static int combineMeasuredStates(int curState, int newState) {
        return curState | newState;
    }

回到編號5的代碼,就是為了將所有子View的state都結(jié)合在一起,這個有什么作用現(xiàn)在也不好講.先繼續(xù)看吧.

編號2的代碼是將FrameLayout中所有visibility屬性不為GONE的子View都進行測量(即在布局中占據(jù)位置的View),使用的是measureChildWithMargins().下面我們來看這個函數(shù)的代碼.

    /**
     * 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 (需要測量的子View)
     * @param parentWidthMeasureSpec The width requirements for this view 
     *      (parent對子View寬度的要求(MeasureSpec))
     * @param widthUsed Extra space that has been used up by the parent
     *        horizontally (possibly by other children of the parent)
     *        (被parent或其他兄弟節(jié)點在布局的水平方向上使用了的尺寸大小)
     * @param parentHeightMeasureSpec The height requirements for this view
     *      (parent對子View高度的要求(MeasureSpec))
     * @param heightUsed Extra space that has been used up by the parent
     *        vertically (possibly by other children of the parent)
     *        (被parent或其他兄弟節(jié)點在布局的垂直方向上使用了的尺寸大小)
     */
    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);
    }

像代碼中的注釋所寫的一樣,這個函數(shù)就是用來告訴child需要進行測量.測量過程中需要遵循parentMeasureSpec,還需要考慮將paddingmargin的值.通過了getChildMeasureSpec()得到了子View的MeasureSpec后就可以調(diào)用子View的measure()進行測量了.所以我們要來看看getChildMeasureSpec(int,int,int)這個函數(shù),先來看看函數(shù)原型的文檔,因為我們要先搞清楚各個參數(shù)所代表的意義.

    /**
     * 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
     *      (對View的尺寸限制MeasureSpec)
     * @param padding The padding of this view for the current dimension and
     *        margins, if applicable
     *        (可以理解為父布局的padding值+View的margin值,即父布局中未使用的尺寸大小)
     * @param childDimension How big the child wants to be in the current
     *        dimension
     *        (View希望在布局中的大小,即子View布局參數(shù)的寬高)
     * @return a MeasureSpec integer for the child
     * 
     */
    public static int getChildMeasureSpec(int spec, int padding, int childDimension)

這個函數(shù)是為了獲得當前View的MeasureSpec以便于進行測量和傳遞給子View的.函數(shù)中主要是根據(jù)父布局的MeasureSpec來創(chuàng)建View自己的MeasureView.下面是相應的代碼.

代碼有點長,希望能完整地看一遍.但如果不想看也不要緊,就粗略地掃一眼吧.因為后面有圖片進行總結(jié),正所謂一圖勝千言啊!

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        // 分別獲取父布局`MeasureSpec`中的模式和尺寸
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        // 獲取父布局實際提供給View的尺寸大小(去除邊距)
        // 即父布局最大的可用的大小
        // 與0相比取最大值,以免尺寸大小為負值
        int size = Math.max(0, specSize - padding);
        
        // 當前View最終的尺寸大小和模式
        int resultSize = 0;
        int resultMode = 0;
        
        // 根據(jù)父布局的模式來決定View的模式和尺寸
        switch (specMode) {
        // Parent has imposed an exact size on us
        // 表示父布局的大小為確切的值
        case MeasureSpec.EXACTLY:
            // 由于`MATCH_PARENT`=-1,`WRAP_CONTENT`=-2,
            // 所以childDimension >= 0 表示View的寬高布局參數(shù)為具體的值
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 表示View的布局參數(shù)為`MATCH_PARENT`,即View希望大小是父布局的最大的可以大小
                // 模式與父布局一樣為EXACTLY
                // 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.
                // 表示View的布局參數(shù)為`WRAP_CONTENT`,那么說明View的大小不明確,需要由它的內(nèi)容決定
                // 所以測量值的尺寸為父布局的最大的可以大小,模式為AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        // 表示父布局的大小不確定,需要由父布局的內(nèi)容決定
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                // 表明View的布局大小為確切的值
                // 所以View的測量大小為布局參數(shù)的值,模式為EXACTLY
                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.
                // 表明View的布局參數(shù)為MATCH_PARENT
                // 所以View的測量大小為父布局的可用大小,模式與父布局一樣為AT_MOST
                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.
                // View的布局參數(shù)為WRAP_CONTENT,即View的測量尺寸大小不確定,由其內(nèi)容決定
                // 所以View的測量大小為父布局最大的可以大小,模式為AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        // 表示父布局對View的大小沒有限制,通常用在ListView等可滾動的控件中
        // 這種情況下父布局會滿足View的所有要求
        
        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;
        }
        // 根據(jù)最終的View的模式和尺寸生成View的MeasureSpec
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

雖然上面的代碼不短,但代碼邏輯并不復雜,而且我已經(jīng)在代碼上做了比較明白的注釋了,如果還是不理解,那就請看圖.


函數(shù)的功能就是為了給View生成一個MeasureSpec類型的int,而這個值是由模式和大小合成的,而且它們的值由父布局MeasureSpec的模式和View的布局大小共同決定.上圖就是一個決定View的ModeSize的過程.
這里有一點需要注意的,就是當父布局的MeasureSpec的模式為UNSPECIFIED時,若View的布局大小不為一個具體的值那么resultSize的大小就為0.這里決定resultSize的值是下面的一條語句.

resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;

而這里的View.sUseZeroUnspecifiedMeasureSpec可以在View中找到相應的信息.

    /**
     * Always return a size of 0 for MeasureSpec values with a mode of UNSPECIFIED
     */
    static boolean sUseZeroUnspecifiedMeasureSpec = false;
    public View(Context context) {
    .....
            // In M and newer, our widgets can pass a "hint" value in the size
            // for UNSPECIFIED MeasureSpecs. This lets child views of scrolling containers
            // know what the expected parent size is going to be, so e.g. list items can size
            // themselves at 1/3 the size of their container. It breaks older apps though,
            // specifically apps that use some popular open source libraries.
            sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < M;
    ......
    }

就是說若當前的Android版本小于M的話那sUseZeroUnspecifiedMeasureSpec的值就為true.所以在舊版本的Android中,resultSize的值都為0.
現(xiàn)在我們回到編號1的地方就可能對那句代碼有所理解了.

        // 編號1. 用于判斷是否需要對布局參數(shù)為MATCH_PARENT的子View進行重新測量
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;

我們現(xiàn)在對應著上面的View的MeasureSpec生成圖來分析代碼.將流程反過來看,即從有編號的框開始看,我們可以看到在(1),(2),(4),(7)4種情況下resultMode的值為EXACTLY.而它們的條件有3種是childDimension >= 0即FrameLayout的布局參數(shù)layout_widthlayout_height為實際的具體值.另一種情況就是FramLayout的布局參數(shù)為MATCH_PARENT且它的父布局為EXACTLY. 綜合的來講可以這么理解吧就是當FrameLayout的布局寬高不同時設置為具體的值,或不同時為MATCH_PARENT那么measureMatchParentChildren這個值就為true.
再簡單的講就是如果FrameLayout的寬高只要有一個設置為WRAP_CONTENT,那么該值就為true.那么就需要對布局參數(shù)為MATCH_PARENT的子View進行重新測量.因為WRAP_CONTENT的情況下父布局的測量值受子View的影響.

能看到這里的同學真是不簡單啊,沒想到第1部分講了這么久,(有的同學就可能會說:這分"神戶牛柳"的量也太多了吧,吃得有點撐啊!)這里篇幅確實有點長,但如果仔細看下來的話還是能學到不少的東西.大家可以先休息一下,待會再來繼續(xù)閱讀.我也在下面做了分割線幫大家標記位置.


Part 2

我們來繼續(xù)我們onMeasure()第2部分代碼的分析吧.相信我,當你看完第1部分的分析后,后面就會很有感覺.

        // 第2部分
        // 前面我們的maxWidth和maxHeight只是計算了子View的外邊距
        // 但沒有計算FrameLayout的內(nèi)邊距,所以在這里加上
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

        // Check against our minimum height and width
        // 保證我們的`maxWidth`和`maxHeight`不會太小(至少要等于最小的建議值)
        // 這里的最少建議值與背景有關(guān)
        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));

第2部分的代碼是先是確定了maxWidthmaxHeight的值,給大家看一眼getSuggestedMinimumHeight()的代碼吧,因為后面這個函數(shù)還會用到,我相信大家很容易就能理解.

protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

我們主要還是看標注的那句代碼,setMeasuredDimension()就是將測量好的寬高值存儲下來供后面的布局過程使用.來看看代碼吧.

    /**
     * <p>This method must be called by {@link #onMeasure(int, int)} to store the
     * measured width and measured height. Failing to do so will trigger an
     * exception at measurement time.</p>
     *
     * @param measuredWidth The measured width of this view.  May be a complex
     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
     * {@link #MEASURED_STATE_TOO_SMALL}.
     * @param measuredHeight The measured height of this view.  May be a complex
     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
     * {@link #MEASURED_STATE_TOO_SMALL}.
     */
    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
    /**
     * Sets the measured dimension without extra processing for things like optical bounds.
     * Useful for reapplying consistent values that have already been cooked with adjustments
     * for optical bounds, etc. such as those from the measurement cache.
     *
     * @param measuredWidth The measured width of this view.  May be a complex
     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
     * {@link #MEASURED_STATE_TOO_SMALL}.
     * @param measuredHeight The measured height of this view.  May be a complex
     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
     * {@link #MEASURED_STATE_TOO_SMALL}.
     */
    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

上面就是相關(guān)的代碼,也比較簡單,setMeasuredDimension()中最后調(diào)用了setMeasuredDimensionRaw()來設置mMeasuredWidthmMeasuredHeight的值.其中關(guān)于Optical Bounds有興趣的同學可以到Internet上搜索一下,或看看下圖.這里我們不作討論,可以跳過.


了解了setMeasuredDimension()后,那我們再來看看調(diào)用處給它傳進的兩個參數(shù)resolveSizeAndState(maxWidth,widthMeasureSpec,childState)resolveSizeAndState(maxWidth,widthMeasureSpec,childState).既然調(diào)用了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) {
                    // 防止View超出了限制的大小所做的處理
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        // 將尺寸大小值和狀態(tài)組合到一起
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }

第1部分我們已經(jīng)讀過了不少的類似的代碼了,在這里我就不啰嗦了.主要還是講講resolveSizeAndState(maxHeight,heightMeasureSpec,childState<<MEASURED_HEIGHT_STATE_SHIFT)這句代碼吧,為什么這里需要進行左移?如果前面有認真看的話就很容易理解,因為我們的childState是存有寬高的狀態(tài)的組合值,我們的高度的狀態(tài)值存在第8-15位,所以這里需要將它左移16位(將狀態(tài)位放置在常規(guī)的位置).

Part 3

來到第3部分了,這部分比較簡單,我們先來看看代碼.

        //第3部分
        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) {
                    // 寬度為總寬度減去父布局的Padding和View的Margin
                    final int width = Math.max(0, getMeasuredWidth()
                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                            - lp.leftMargin - lp.rightMargin);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            width, MeasureSpec.EXACTLY);
                } else {
                    // 第1部分已經(jīng)討論過
                    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);
            }
        }

這里的代碼就是當FrameLayout有多于1個的子View的布局寬高為MATCH_PARENT時(并且滿足第1部分中的measureMatchParentChildrentrue),即當FrameLayout的寬高設置存在WRAP_CONTENT時,對子View進行重新的測量.

View的onMeasure()

到這里我們FrameLayoutonMeasure()已經(jīng)分析完畢了,既然我們已經(jīng)分析過了ViewGrouponMeasure(),那ViewonMeasure()我覺得也免不了,廢話不說趕緊上菜.

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

代碼還是很簡單的,就一句.雖然嵌套很多,但只有一個函數(shù)我們沒有見過,就是getDefaultSize(),那就看看它是何方神圣.

    /**
     * Utility to return a default size. Uses the supplied size if the
     * MeasureSpec imposed no constraints. Will get larger if allowed
     * by the MeasureSpec.
     *
     * @param size Default size for this view
     * @param measureSpec Constraints imposed by the parent
     * @return The size this view should be.
     */
    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;
    }

這個函數(shù)是決定View的MeasureSpecSize的一種默認的方法.代碼非常的簡單,我就不再多說了.因為今天看這種代碼看得真的不少,我相信很多同學都快要看吐了.
不過到這里我很開心,因為我們這次的任務完成了,measure的過程我們已經(jīng)分析完了.

最后的甜點(總結(jié))

牛柳吃完了,不知道大家能不能消化,所以最后給大家上個甜點吧.最后還是用圖說話,來總結(jié)一下measure的整個流程.

measure

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子途样,更是在濱河造成了極大的恐慌顶捷,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件靖榕,死亡現(xiàn)場離奇詭異兼犯,居然都是意外死亡强衡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門合呐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來暮的,“玉大人,你說我怎么就攤上這事淌实《潮纾” “怎么了猖腕?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長微猖。 經(jīng)常有香客問我谈息,道長,這世上最難降的妖魔是什么凛剥? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任侠仇,我火速辦了婚禮,結(jié)果婚禮上犁珠,老公的妹妹穿的比我還像新娘逻炊。我一直安慰自己,他們只是感情好犁享,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布余素。 她就那樣靜靜地躺著,像睡著了一般炊昆。 火紅的嫁衣襯著肌膚如雪桨吊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天凤巨,我揣著相機與錄音视乐,去河邊找鬼。 笑死敢茁,一個胖子當著我的面吹牛佑淀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播彰檬,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼伸刃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了逢倍?” 一聲冷哼從身側(cè)響起捧颅,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎较雕,沒想到半個月后隘道,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡郎笆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年谭梗,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宛蚓。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡激捏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凄吏,到底是詐尸還是另有隱情远舅,我是刑警寧澤闰蛔,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站图柏,受9級特大地震影響序六,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蚤吹,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一例诀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧裁着,春花似錦繁涂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至桶雀,卻和暖如春矿酵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背矗积。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工全肮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人漠魏。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓倔矾,卻偏偏與公主長得像妄均,于是被迫代替她去往敵國和親柱锹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

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