Android View的測量,布局乖寒,繪制(一)

前言

通過前面兩個章節(jié)的學(xué)習(xí)猴蹂,知道了Activity的生命周期函數(shù)的調(diào)用,和布局文件的加載楣嘁。但是并沒有看到View的繪制磅轻,那View的繪制是在什么時候的呢?

這邊文章需要小伙伴們WindowManagerService(WMS) 相關(guān)知識所了解逐虚。
Window我們應(yīng)該很熟悉聋溜,它是一個抽象類,具體的實現(xiàn)類為PhoneWindow叭爱,它對View進行管理撮躁。 WindowManager是一個接口類,繼承自接口ViewManager买雾,從名稱就知道它是用來管理Window的把曼,它的實現(xiàn)類為WindowManagerImpl。如果我們想要對Window進行添加和刪除就可以使用WindowManager漓穿,具體的工作都是由WMS來處理的嗤军,WindowManager和WMS通過Binder來進行跨進程通信

在ActivityThread中會有一個handleResumeActivity()方法,而這個方法晃危,也是Activity的onResume()方法的入口叙赚。但這個方法不單是調(diào)用了Activity的onResume()方法,它還設(shè)計到View的測量山害,布局纠俭,和繪制。

##ActivityThread
@Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        ...
        //調(diào)用Activity的onResume()方法
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        ...
        //獲取到Activity對象
        final Activity a = r.activity;
        ...
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();  //獲取PhoneWindow
            View decor = r.window.getDecorView();  //獲取DecorView
            decor.setVisibility(View.INVISIBLE);
            //WindowManagerImpl是ViewManager實現(xiàn)類
            ViewManager wm = a.getWindowManager();  //1
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            ...
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);  //2
                } else {
                    ...
                }
            }
            ...
        } else if (!willBeVisible) {
            if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true;
        }
        ...
}

注釋1
獲取到ViewManager對象浪慌,ViewManager是一個接口冤荆,WindowManager是他的一個子類,而WindowManagerImpl是WindowManager的實現(xiàn)類权纤,所以這里其實真正獲取到的是WindowManagerImpl钓简,一路跟蹤getWindowManager方法可以看到乌妒。WindowManagerImpl對象的作用是接替的WindowManager類的工作管理窗口。
注釋2

##WindowManagerImpl
@Override 
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        //mGlobal 是WindowManagerGlobal對象
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);  
}

最終調(diào)用了WindowManagerGlobal的addView方法外邓,可以看出WindowManagerImpl雖然是WindowManager的實現(xiàn)類撤蚊,但是卻沒有實現(xiàn)什么功能,而是將功能實現(xiàn)委托給了WindowManagerGlobal损话,這里用到的是橋接模式侦啸。

為了幫助小伙伴們的理解,這里再給大家補充一張Window和WindowManager的關(guān)系圖丧枪。


Window和WindowManager的關(guān)系.png

接下來光涂,再來看看WindowManagerGlobal的addView()方法到底做了哪些事情。

##WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
       ...
        ViewRootImpl root;
        View panelParentView = null;
        ...
        root = new ViewRootImpl(view.getContext(), display);  //1
        
        view.setLayoutParams(wparams);  //2

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        // do this last because it fires off messages to start doing things
        try {
             root.setView(view, wparams, panelParentView);  //3
        } catch (RuntimeException e) {
             // BadTokenException or InvalidDisplayException, clean up.
             if (index >= 0) {
                    removeViewLocked(index, true);
            }
                throw e;
            }
        }
}

注釋1
創(chuàng)建ViewRootImpl對象拧烦,ViewRootImpl身負了很多職責(zé):

  1. View樹的根并管理View樹
  2. 觸發(fā)View的測量忘闻、布局和繪制
  3. 輸入事件的中轉(zhuǎn)站
  4. 管理Surface
  5. ViewRootImpl對象是鏈接WMS和DecorView的紐帶

注釋2
view指的是DecorView,這里是設(shè)置DecorView的LayoutParams恋博,而這個LayoutParams是WindowManager.LayoutParams也就是當前窗口的布局參數(shù)齐佳,如當前窗口的寬高。

注釋3债沮,ViewRootImpl對象與DecorView 對象關(guān)聯(lián)炼吴。

##ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ...
                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();  //1
                ...
                try {
                    ...
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);  //2
                } catch (RemoteException e) {
                    ...
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
                ...
            }
        }
}

setView方法中有很多邏輯,這里只截取了一小部分疫衩,主要就是調(diào)用了requestLayout()缺厉,和mWindowSession的addToDisplay方法。mWindowSession的addToDisplay()方法將在下個章節(jié)給大家介紹隧土。本章主要分析requestLayout()

View的測量,布局命爬,繪制

##ViewRootImpl
@Override
public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();  //1
            mLayoutRequested = true;
            scheduleTraversals();  //2
        }
}

注釋1曹傀,檢測當前線程是否是主線程,不是拋出異常
注釋2饲宛,遍歷View樹

##ViewRootImpl
void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);  //1
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
}

注釋1調(diào)用 postCallback方法的調(diào)用皆愉,內(nèi)部代碼實現(xiàn)的邏輯是使用Handler對象來發(fā)送消息。而在postCallback方法的參數(shù)中mTraversalRunnable對象是一個Runnable對象艇抠。由此可見幕庐,這里具體的業(yè)務(wù)代碼是存在mTraversalRunnable對象的run方法中。

##ViewRootImpl.TraversalRunnable 
final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
}

##ViewRootImpl
void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals(); //1

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
}

注釋1:真證開始執(zhí)行View樹的遍歷

##ViewRootImpl
private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;  //mView是DecorView對象
        ...
        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
            ...
            if (!mStopped || mReportNextDraw) {
                ...
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                    //獲取根布局的測量規(guī)格
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
                    ...
                    // Ask host how big it wants to be
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //1
                    ...
                }
            }
        } else {
           ...
        }

        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            performLayout(lp, mWidth, mHeight); //2
            ...
        }
        ...
        if (!cancelDraw && !newSurface) {
            ...
            performDraw();  //3
        } else {
            ...
        }
        ...
}

performTraversals方法很長家淤,主要關(guān)注的是三個方法异剥,performMeasure方法對View的測量,performLayout方法View的布局絮重,performDraw方法View的繪制冤寿。

注釋1 View的測量
在performMeasure方法中歹苦,會傳入兩個參數(shù),是指傳入寬高的測量規(guī)格督怜。這兩個參數(shù)其實都是通過調(diào)用getRootMeasureSpec方法來獲取的殴瘦。

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

在getRootMeasureSpec方法中,傳入了兩個參數(shù)号杠,第一個是當前窗口的大序揭浮(寬或高),第二步是根布局的尺寸規(guī)則(如:match_parent或wrap_content分別用-1和-2表示)姨蟋。根據(jù)尺寸規(guī)則獲取根測量規(guī)格屉凯。

打開MeasureSpec類

##View.MeasureSpec
public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

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

    /**
     * UNSPECIFIED 模式:
     * 父View不對子View有任何限制,子View需要多大就多大
     */
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;

    /**
     * EXACTYLY 模式:
     * 父View已經(jīng)測量出子Viwe所需要的精確大小芬探,這時候View的最終大小
     * 就是SpecSize所指定的值神得。對應(yīng)于match_parent和精確數(shù)值這兩種模式
     */
    public static final int EXACTLY     = 1 << MODE_SHIFT;

    /**
     * AT_MOST 模式:
     * 子View的最終大小是父View指定的SpecSize值,并且子View的大小不能大于這個值偷仿,
     * 即對應(yīng)wrap_content這種模式
     */
    public static final int AT_MOST     = 2 << MODE_SHIFT;

    //將size和mode打包成一個32位的int型數(shù)值
    //高2位表示SpecMode哩簿,測量模式,低30位表示SpecSize酝静,某種測量模式下的規(guī)格大小
    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);
        }
    }


    //將32位的MeasureSpec解包节榜,返回SpecMode,測量模式
    @MeasureSpecMode
    public static int getMode(int measureSpec) {
        //noinspection ResourceType
        return (measureSpec & MODE_MASK);
    }

    //將32位的MeasureSpec解包,返回SpecSize别智,某種測量模式下的規(guī)格大小
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }

    ...

    /**測量模式
    EXACTLY :父容器已經(jīng)測量出所需要的精確大小宗苍,這也是childview的最終大小
            ------match_parent,精確值是父View寬高

    ATMOST : child view最終的大小不能超過父容器的給的
            ------wrap_content 精確值不超過父View寬高

    UNSPECIFIED: 不確定薄榛,源碼內(nèi)部使用
            -------一般在ScrollView讳窟,ListView
    **/
}

這里會出現(xiàn)一個MeasureSpec對象是Android view測量系統(tǒng)的重要的一個元素,內(nèi)部維持著是一個32位的int值敞恋,高兩位代表測量模式SpecMode(MeasureSpec.EXACTLY丽啡、MeasureSpec.AT_MOST、MeasureSpec.UNSPECIFIED )硬猫,低30位代表測量的大小SpecSize补箍,MeasureSpec用一個int值同時存放了兩個信息,一是在onMeasure中根據(jù)這個MeasureSpec來確定view的測量寬高啸蜜,二可以節(jié)省內(nèi)存的開銷坑雅。

三個主要的方法
makeMeasureSpec()--負責(zé)打包mode和size
getMode()--負責(zé)解析得到mode部分
getSize()--負責(zé)解析得到size部分

那么makeMeasureSpec方法中第一個參數(shù)傳遞的是一個窗口的size,第二個參數(shù)是幾種測量模式:EXACTLY ,ATMOST ,UNSPECIFIED這三個模式的本質(zhì)是0,1,2的左位移30位衬横,
那么其實我們先能理解為我們實際上在傳遞值得過程當中將顯示模式+size打包一起交給measure方法
而里面的數(shù)據(jù)結(jié)構(gòu)其實實際上是一個32位的數(shù)值裹粤,
我們可以明顯看到幾個模式的值在后面進行了左位移操作了30位
用MODE_SHIFT 操作之后,實際表明一個32位的值30前兩位作為MODE
而MODE_MASK 表示是后30位
所以現(xiàn)在能得出一個結(jié)論他們的數(shù)據(jù)構(gòu)可以看成是


規(guī)格.png

而這個時候我們看到有三個方法負責(zé)打包解析
makeMeasureSpec--負責(zé)打包mode和size
getMode--負責(zé)解析得到mode部分
getSize--負責(zé)解析得到size部分

那么打包時運用了(size & ~MODE_MASK) | (mode & MODE_MASK)的算法進行混合,那么這里我們可以認為 是size 轉(zhuǎn)化成32位后放入后30位 組合 mode(mode放入前兩位)


位運算或.png

getMode解析用了measureSpec & MODE_MASK(解析只要前2位)


位運算與.png

getSize解析用了measureSpec & ~MODE_MASK(不要前兩位)


位運算非.png

那么此處就可以得到蜂林,在測量時他真正的給我傳遞的是規(guī)格蛹尝,而所謂的規(guī)格只不過是顯示模式+實際寬高的一個數(shù)據(jù)包后豫,或者你理解為這個值當中包含模式和具體數(shù)值就行了

所以我們得出一個結(jié)論,View的測量流程中突那,通過makeMeasureSpec來保存寬高信息挫酿,在其他流程通過getMode或getSize得到模式和寬高。那么問題來了愕难,上面提到MeasureSpec是子容器和父容器的模式所共同影響的早龟,那么,對于DecorView來說猫缭,它已經(jīng)是頂層view了葱弟,沒有父容器,那么它的MeasureSpec怎么來的呢猜丹? 是否還記得我們的setContent所加載的系統(tǒng)布局芝加?


1_Activity加載UI-類圖關(guān)系和視圖結(jié)構(gòu).png

我們的初始就已經(jīng)給了一個布局去裝載,所以射窒,在這里藏杖,他的父布局是系統(tǒng)布局。

那么到目前為止脉顿,就已經(jīng)獲得了一份DecorView的MeasureSpec蝌麸,它代表著根View的規(guī)格、尺寸艾疟,在接下來的measure流程中来吩,就是根據(jù)已獲得的根View的MeasureSpec來逐層測量各個子View。來到performMeasure方法蔽莱,看看它做了什么工作弟疆。

##ViewRootImpl
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);  //1
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
}

注釋1這里很明顯他直接調(diào)用搞得是view的measure這里的mView就是DecorView,也就是說盗冷,從頂級View開始了測量流程兽间。

##View
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
         ...
        if (forceLayout || needsLayout) {
            ...
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
               ...
            }
            ...
        }
        ...
}

可以看到,它在內(nèi)部調(diào)用了onMeasure方法正塌。那么注意,到此為止恤溶,我們的布局容器的核心就在這里了乓诽,不管是LinearLayout或者是FreamLayout還是其他布局容器(ViewGroup),他們都是通過測量組件咒程,實現(xiàn)View的大小測量鸠天,每一個布局容器(ViewGroup)的onMeasure實現(xiàn)都不一樣,這里因為頂層DecorView是一個FreamLayout帐姻。以FrameLayout為例稠集。

舉例
##FrameLayout  
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //獲取當前布局內(nèi)的子View數(shù)量
        int count = getChildCount();
       /**
         *  判斷當前布局的寬高是否是MATCH_PARENT模式或者指定一個精確的大小奶段,如果是則置
         *  measureMatchParent為false.
         *
         *  也就是當前FrameLayout,為WRAP_CONTENT時為true
         */
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();

        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;
        //遍歷所有類型不為GONE的子View
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                 //對每一個子View進行測量,傳入子View和父容器的寬高規(guī)格
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);  //1
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                /**
                 * 尋找子View中寬高的最大者,因為如果FrameLayout是wrap_content屬性
                 * 那么它的大小取決于子View中的最大者
                 */
                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());
                /**
                 * 如果FrameLayout是WRAP_CONTENT模式剥纷,那么往mMatchParentChildren中添加
                 * 寬或者高為match_parent的子View痹籍,因為該子View的最終測量大小會受到FrameLayout的最終測量大小影響
                 */
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }
        ...
        //保存測量結(jié)果
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
         /**
         * 子View中設(shè)置為WRAP_CONTENT的個數(shù)
         * 
         * 只有FrameLayout的模式為wrap_content的時候才會執(zhí)行下列語句 if (count > 1)
         * 如果存在count>1,mMatchParentChildren中添加的子View設(shè)置為MATCH_PARENT或者
         * 精確數(shù)據(jù)
         */
        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();
                //對FrameLayout的寬度規(guī)格設(shè)置晦鞋,因為這會影響子View的測量
                final int childWidthMeasureSpec;
                /**
                 * 如果子View的寬度是match_parent屬性蹲缠,那么對當前View的寬度規(guī)格修改為:
                 * 總寬度(父布局寬) - padding - margin,這樣做的意思是:
                 * 對于子Viw來說悠垛,如果要match_parent线定,那么它可以覆蓋的范圍是父布局的寬度
                 * 減去padding和margin后剩下的空間。childWidthMeasureSpec是FrameLayout的中子View的規(guī)格
                 *
                 * 如果子View的寬度是一個確定的值确买,比如100dp斤讥,那么FrameLayout中childWidthMeasureSpec的寬度規(guī)格修改為:
                 * View的寬度,即100dp湾趾,SpecMode為EXACTLY模式
                 */
                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);
                }
                //對于這部分的子View需要重新進行measure過程
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
}

onMeasure方法總結(jié):首先芭商,F(xiàn)rameLayout根據(jù)它的測量規(guī)則來對每一個子View進行測量,即調(diào)用measureChildWithMargin方法撑帖,這個方法下面會詳細說明蓉坎;對于每一個測量完成的子View,會尋找其中最大的寬高胡嘿,那么FrameLayout的測量寬高會受到這個子View的最大寬高的影響(wrap_content模式)蛉艾,接著調(diào)用setMeasureDimension方法,把FrameLayout的測量寬高保存衷敌。最后則是特殊情況的處理勿侯,即當FrameLayout為wrap_content屬性時,如果其子View是match_parent屬性的話缴罗,則要重新設(shè)置FrameLayout的測量規(guī)格助琐,然后重新對該部分View測量。

注釋1
measureChildWithMargin方法面氓,它接收的主要參數(shù)是子View以及父容器的MeasureSpec(測量規(guī)格)兵钮,所以它的作用就是對子View進行測量,那么我們直接看這個方法

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

里面調(diào)用了getChildMeasureSpec方法舌界,把父容器的MeasureSpec以及自身的layoutParams屬性傳遞進去來獲取當前子View的MeasureSpe掘譬,在這里我們可以看到直接又調(diào)用了子View的measure方法。

總結(jié):那么現(xiàn)在我們能得到整體的測量流程:在performTraversals開始獲得DecorView種的系統(tǒng)布局的尺寸呻拌,然后在performMeasure方法中開始測量流程葱轩,對于不同的layout布局有著不同的實現(xiàn)方式,但大體上是在onMeasure方法中,對每一個子View進行遍歷靴拱,根據(jù)父容器(ViewGroup)的MeasureSpec及子View的layoutParams來確定自身的測量寬高垃喊。然后根據(jù)所有子View的測量寬高信息再確定父容器(ViewGroup)的寬高。

作者:Alan
原創(chuàng)博客袜炕,請注明轉(zhuǎn)載處....

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末本谜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子妇蛀,更是在濱河造成了極大的恐慌耕突,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件评架,死亡現(xiàn)場離奇詭異眷茁,居然都是意外死亡,警方通過查閱死者的電腦和手機纵诞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門上祈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浙芙,你說我怎么就攤上這事登刺。” “怎么了嗡呼?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵纸俭,是天一觀的道長。 經(jīng)常有香客問我南窗,道長揍很,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任万伤,我火速辦了婚禮窒悔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘敌买。我一直安慰自己简珠,他們只是感情好,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布虹钮。 她就那樣靜靜地躺著聋庵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪芙粱。 梳的紋絲不亂的頭發(fā)上祭玉,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音宅倒,去河邊找鬼。 笑死,一個胖子當著我的面吹牛拐迁,可吹牛的內(nèi)容都是我干的蹭劈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼线召,長吁一口氣:“原來是場噩夢啊……” “哼铺韧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起缓淹,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤哈打,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后讯壶,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體料仗,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年伏蚊,在試婚紗的時候發(fā)現(xiàn)自己被綠了立轧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡躏吊,死狀恐怖氛改,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情比伏,我是刑警寧澤胜卤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站赁项,受9級特大地震影響葛躏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜肤舞,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一紫新、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧李剖,春花似錦芒率、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至德玫,卻和暖如春匪蟀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宰僧。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工材彪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓段化,卻偏偏與公主長得像嘁捷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子显熏,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

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