View的繪制流程 - onMeasure()源碼分析

前言

View繪制流程系列文章
View的繪制流程 - onMeasure()源碼分析
View的繪制流程 - onLayout()源碼分析
View的繪制流程 - onDraw()源碼分析

結(jié)論


View的繪制流程都是從ViewRootImpl中的requestLayout()方法開(kāi)始進(jìn)去的浮创,performMeasure()妓笙、performLayout()测蘑、performDraw()斗塘,而如果代碼中又寫(xiě)了這樣的代碼:addView()、setVisibility()等方法签杈,意思就是會(huì)重新執(zhí)行requestLayout(),意思就是會(huì)重新執(zhí)行View的繪制流程,這個(gè)時(shí)候執(zhí)行View的繪制流程時(shí)不會(huì)和第一次一樣去執(zhí)行所有的邏輯侣姆,比如說(shuō)你自己addView()生真,一次性添加了10個(gè)View,那么它有可能等你添加完畢之后才去執(zhí)行 View的繪制流程的:

測(cè)量是從外往里遞歸捺宗,也就是說(shuō):
ViewRootImpl會(huì)把自己的測(cè)量模式傳遞給 -> DecorView柱蟀,然后DecorView會(huì)把自己的測(cè)量模式傳遞給 activity_main中的LinearLayout ->
然后LinearLayout通過(guò)for循環(huán)把自己的測(cè)量模式傳遞給 TextView,然后就會(huì)調(diào)用 TextView的onMeasure()方法蚜厉,然后根據(jù)傳遞過(guò)來(lái)的LinearLayout的測(cè)量模式來(lái)指定 TextView的寬高长已,測(cè)量完畢后通過(guò)childHeight = child.getMeasuredHeight();獲取到 子View的寬高,即就是獲取到3個(gè)TextView的寬高后昼牛,來(lái)計(jì)算父布局术瓮,即就是LinearLayout
自己的寬高 ,然后再把自己的寬高向外傳遞給DecorView ->
然后DecorView根據(jù) LinearLayout傳回來(lái)的寬高贰健,然后計(jì)算自己的寬高 , 把自己寬高計(jì)算好后胞四,然后再把自己的寬高向外傳遞給 ViewRootImpl 。


onMeasure()源碼分析總結(jié)如下:
測(cè)量是從外往里遞歸的:
從外往里:

首先從最外層的 ViewRootImpl開(kāi)始伶椿,它把它的 測(cè)量模式傳遞給 DecorView撬讽,然后DecorView把自己的測(cè)量模式 傳遞給 父布局LinearLayout,然后LinearLayout再把自己的測(cè)量模式 傳遞給 子View悬垃;

從里往外:

等子View計(jì)算出自己寬高后游昼,然后把自己寬高傳遞給父布局LinearLayout,然后LinearLayout根據(jù) 子View的寬高尝蠕,來(lái)計(jì)算自己的寬高烘豌,這里計(jì)算方式就是:
如果父布局是 LinearLayout,且是垂直方向看彼,父布局高度就是累加子布局高度廊佩;
如果父布局是RelativeLayout,那么父布局高度就是指定 子孩子中最高的靖榕;
标锄,然后LinearLayout把自己的高度傳遞給 它的父布局,就是這樣一路都把自己的高度傳遞給父布局茁计,最后傳遞給 DecorView料皇、傳遞給 ViewRootImpl,

onMeasure()源碼中就是這樣測(cè)量的星压,如下圖所示:
onMeasure()源碼分析.png

下邊進(jìn)行分析践剂,最下邊的結(jié)論可以不看,因?yàn)楹蜕线呥@個(gè)一樣娜膘,下邊僅用于分析流程逊脯。

1. 說(shuō)明


這節(jié)課來(lái)看下View的繪制流程,我們由下邊的套路來(lái)一步一步引出并分析View的繪制流程 —— 根據(jù)一個(gè)小示例竣贪,如何能獲取mTextViewHeight高度军洼,來(lái)引出setContentView到底做了什么巩螃、Activity的啟動(dòng)流程、最后引出View的繪制流程(即就是分析onMeasure()匕争、onLayout()牺六、onDraw());

2. 代碼如下

public class MainActivity extends AppCompatActivity {

    private TextView text_view;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        // 下邊這個(gè)獲取不到view的高度汗捡,因?yàn)閰?shù)3是null淑际,即就是父布局是null,說(shuō)明你還沒(méi)有把a(bǔ)ctivity_main添加到父布局中扇住,所以不能獲取到寬高
        View view = View.inflate(this, R.layout.activity_main, null);


        // 這個(gè)可以獲取到寬高春缕,因?yàn)?參數(shù)3ViewGroup表示父布局,下邊代碼就表示艘蹋,你已經(jīng)把a(bǔ)ctivity_main布局添加到父布局中了锄贼,所以可以獲取到寬高
//        View view = View.inflate(this, R.layout.activity_main, ViewGroup);


        text_view = (TextView) findViewById(R.id.text_view);
        Log.e("TAG" , "height1 -> " + text_view.getMeasuredHeight()) ;   // 0

        text_view.post(new Runnable() {
            @Override
            public void run() {
                Log.e("TAG" , "height2 -> " + text_view.getMeasuredHeight()) ;  // 高度:51
            }
        }) ;
    }


    @Override
    protected void onResume() {
        super.onResume();
        Log.e("TAG" , "height3 -> " + text_view.getMeasuredHeight()) ;  // 0
    }
}

View view = View.inflate(this, R.layout.activity_main, null)為什么獲取不到高度?

參數(shù)3表示父布局女阀,而這里的參數(shù)3是null宅荤,表示沒(méi)有把a(bǔ)ctivity_main添加到父布局中,所以不能獲取到寬高浸策;

View view = View.inflate(this, R.layout.activity_main, ViewGroup)為什么可以獲取到高度冯键?

參數(shù)3表示父布局,這里的參數(shù)3是 ViewGroup庸汗,表示父布局惫确,這里為了形象表示就直接把父布局寫(xiě)成了ViewGroup,其實(shí)只要是父布局就行蚯舱。這里就表示把a(bǔ)ctivity_main添加到父布局中改化,所以可以獲取到高度;

分析其余3個(gè)mTextViewHeight的高度:

由以上可知:

03-19 21:29:23.491 18696-18696/? E/TAG: height1 -> 0
03-19 21:29:23.492 18696-18696/? E/TAG: height3 -> 0
03-19 21:29:23.591 18696-18696/? E/TAG: height2 -> 51

height1 = 0枉昏;height3 = 0 陈肛;height2 = 51(高度)
分析原因:
我們需要知道,我們?cè)趏nCreate()方法中只是調(diào)用了setContentView()兄裂,也需要知道setContentView()到底干了什么句旱?
在PhoneWindow中,setContentView只是new DecorView()

之所以能夠拿到控件的寬高懦窘,是因?yàn)檎{(diào)用了onMeasure()方法前翎,而在我們之前寫(xiě)的那些自定義View效果的時(shí)候稚配,其實(shí)都是在 onMeasure()方法中獲取到寬高后畅涂,都會(huì)重新調(diào)用setMeasuredDimension(width , height);
setContentView 只是創(chuàng)建DecorView,并且把我們的布局加載進(jìn)DecorView道川,并沒(méi)有調(diào)用onMeasure()方法午衰;

分析PhoneWindow的源碼如下:

@Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

只要installDecor()方法執(zhí)行完立宜,就會(huì)形成這樣一個(gè)局面:


圖片.png
onCreate()中為什么獲取不到 mTextViewHeight 高度?

因?yàn)樵赑honeWindow中臊岸,setContentView()只是new DecorView()橙数,然后把我們的布局加載到了DecorView(),其余什么都沒(méi)做帅戒,也并沒(méi)有調(diào)用onMeasure()方法灯帮,所以在onCreate()方法中不能獲取到TextView的寬高;


onResume()中為什么也獲取不到 mTextViewHeight 高度逻住?

這個(gè)其實(shí)就涉及到Activity的啟動(dòng)流程的分析钟哥,通過(guò)下邊對(duì)Activity啟動(dòng)流程的分析,即就是分析 ActivityThread源碼瞎访,可以知道:
Activity的啟動(dòng)流程是:
先調(diào)用handleLaunchActivity()腻贰,在這個(gè)方法中調(diào)用performLaunchActivity(),在performLaunchActivity()中會(huì)調(diào)用onCreate() ->
然后調(diào)用handleResumeActivity()扒秸,在這個(gè)方法中調(diào)用performResumeActivity() ->
然后調(diào)用Activity的onResume() ->
然后調(diào)用 wm.addView(decor , 1) 播演,這個(gè)時(shí)候才開(kāi)始把我們的DecorView 加載到 WindowManager中,View的繪制流程在這個(gè)時(shí)候才剛剛開(kāi)始伴奥,才開(kāi)始o(jì)nMeasure()(測(cè)量)写烤、onLayout()(擺放)、onDraw()(繪制)draw()自己拾徙、draw()孩子顶霞;

所以說(shuō)View的繪制流程是在onResume()方法之后才開(kāi)始,所以說(shuō)在onResume()方法中也是不能獲取 mTextViewHeight高度的锣吼,必須要等調(diào)用完onResume()之后选浑,才可以獲取寬高的。

下邊的text_view.post為什么可以獲取到寬高玄叠?
text_view.post(new Runnable() {
            @Override
            public void run() {
                Log.e("TAG" , "height2 -> " + text_view.getMeasuredHeight()) ;  // 高度:51
            }
        }) ;

源碼分析:
View中源碼:

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        getRunQueue().post(action);
        return true;
    }

View中源碼:

public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }

View中源碼:

    /**
     * @param info the {@link android.view.View.AttachInfo} to associated with
     *        this view
     */
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
        mWindowAttachCount++;
        // We will need to evaluate the drawable state at least once.
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        if (mFloatingTreeObserver != null) {
            info.mTreeObserver.merge(mFloatingTreeObserver);
            mFloatingTreeObserver = null;
        }
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();
    }

HandlerActionQueue源碼:

public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }

這里只是把Runnable保存到Queue中古徒,什么都沒(méi)干,run()方法會(huì)在dispatchAttachedToWindow()方法會(huì)在測(cè)量完畢然后調(diào)用executeActions()方法读恃,即就是onMeasure()方法之后調(diào)用executeActions()方法隧膘,所以只要一調(diào)用text_view.post(new Runnable()) ,就馬上可以獲取寬高寺惫。

3:Activity的啟動(dòng)流程疹吃?

這是ActivityThread源碼

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;

        if (r.profilerInfo != null) {
            mProfiler.setProfiler(r.profilerInfo);
            mProfiler.startProfiling();
        }

        // Make sure we are running with the most recent config.
        handleConfigurationChanged(null, null);

        if (localLOGV) Slog.v(
            TAG, "Handling launch of " + r);

        // Initialize before creating the activity
        WindowManagerGlobal.initialize();

        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            Bundle oldState = r.state;
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
        } else {
        }
    }
final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
            return;
        }

        // TODO Push resumeArgs into the activity for consideration
        r = performResumeActivity(token, clearHide, reason);

        if (r != null) {
            final Activity a = r.activity;                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;

            if (!willBeVisible) {
                try {
                    willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
                            a.getActivityToken());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
                }
                if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }
            }
    }

分析ActivityThread源碼可知:
先調(diào)用handleLaunchActivity(),在這個(gè)方法中調(diào)用performLaunchActivity()西雀,在performLaunchActivity()中會(huì)調(diào)用onCreate() ->
然后調(diào)用handleResumeActivity()萨驶,在這個(gè)方法中調(diào)用performResumeActivity() ->
然后調(diào)用Activity的onResume() ->
然后調(diào)用 wm.addView(decor , 1) ,這個(gè)時(shí)候才開(kāi)始把我們的DecorView 加載到 WindowManager中艇肴,View的繪制流程在這個(gè)時(shí)候才剛剛開(kāi)始腔呜,也就是說(shuō)在這個(gè)時(shí)候才開(kāi)始o(jì)nMeasure()(測(cè)量)叁温、onLayout()(擺放)、onDraw()(繪制)draw()自己核畴、draw()孩子膝但;

所以說(shuō)View的繪制流程是在 onResume()之后才開(kāi)始,如果我們以后想要獲取控件的寬高的話谤草,就必須等調(diào)用完onResume()之后跟束,再去獲取寬高就可以。

自定義View的入口就是ViewRootImpl中的requestLayout()方法丑孩,所以先來(lái)看下ViewRootImpl的關(guān)系泳炉,如下圖所示:

ViewRootImpl包裹著DecorView.png

在WindowManagerImpl源碼中:

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

        ViewRootImpl root;
        View panelParentView = null;

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            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);
        } catch (RuntimeException e) {
            }
            throw e;
        }
    }

分析以上源碼可知:
wm.addView(decor , 1) ->
調(diào)用WindowManagerImpl.addView() ->
然后調(diào)用root.setView(view, wparams, panelParentView)方法 ->
調(diào)用requestLayout() -> 調(diào)用scheduleTraversals() ->
調(diào)用doTraversal() -> performTraversals() (網(wǎng)上的文章都是從這個(gè)方法開(kāi)始講解的)

4. 開(kāi)始View的繪制流程

1>:onMeasure()源碼分析:

ViewRootImpl源碼如下:

    /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

                mAttachInfo.mDisplayState = mDisplay.getState();
                mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);

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

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            performTraversals();
        }
    }
private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;

        if (DBG) {
            System.out.println("======================================");
            System.out.println("performTraversals");
            host.debug();
        }

                    if (measureAgain) {
                        if (DEBUG_LAYOUT) Log.v(mTag,
                                "And hey let's measure once more: width=" + width
                                + " height=" + height);
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }
            }
        } else 
        if (didLayout) {
            performLayout(lp, mWidth, mHeight);
            }

            performDraw();
        } else {
            
        }

        mIsInTraversal = false;
    }

LinearLayout的onMeasure()源碼:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

LinearLayout的

void measureChildBeforeLayout(View child, int childIndex,
            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
            int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);
    }
 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);
    }

由上邊我們分析到 WindowManagerImpl的performTraversals()方法,這個(gè)時(shí)候就正式開(kāi)始了View的繪制流程嚎杨;

第一個(gè)調(diào)用performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 這個(gè)方法用于給控件指定寬高 ->

調(diào)用mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); ->

調(diào)用onMeasure(widthMeasureSpec, heightMeasureSpec); 這個(gè)時(shí)候測(cè)量正式開(kāi)始 ->
調(diào)用父布局花鹅,即就是LinearLayout.onMeasure()方法(因?yàn)檫@里是以LinearLayout包裹了3個(gè)TextView為例,當(dāng)然你用RelativeLayout包裹枫浙,就調(diào)用RelativeLayout.onMeasure()測(cè)量方法也是可以的) ->

調(diào)用 LinearLayout.onMeasure()中的measureVertical(widthMeasureSpec, heightMeasureSpec) (這個(gè)是activity_main文件中最外層根布局中的LinearLayout) ->

measureChildWithMargins() ->

調(diào)用child.measure(childWidthMeasureSpec, childHeightMeasureSpec); (這個(gè)是最外層根布局中的子孩子的LinearLayout刨肃,如果有多個(gè)LinearLayout的子孩子,那么就會(huì)一直調(diào)用這個(gè)方法) ->

調(diào)用TextView的onMeasure()(這個(gè)就是子LinearLayout包裹的子孩子TextView)

在上邊涉及到2個(gè)測(cè)量模式

childWidthMeasureSpec, childHeightMeasureSpec

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);
    }
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        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:
            if (childDimension >= 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:
            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;

        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);

由以上源碼可知:

childWidthMeasureSpec, childHeightMeasureSpec這兩個(gè)測(cè)量模式是通過(guò)getChildMeasureSpec()方法去計(jì)算的箩帚,具體計(jì)算是:

在getChildMeasureSpec()中真友,先獲取自己的測(cè)量模式和大小(即就是父布局)紧帕,判斷自己的測(cè)量模式是match_parent或者是一個(gè)固定的值盔然,然后回去判斷子孩子的測(cè)量模式和大小,具體判斷方式如下:

如果自己測(cè)量模式(即就是父布局)是 EXACTLY是嗜,并且子孩子的大小是match_parent愈案,就給子孩子的測(cè)量模式EXACTLY;
如果自己測(cè)量模式(即就是父布局)是 EXACTLY鹅搪,并且子孩子的大小是wrap_content站绪,就給子孩子的測(cè)量模式是 AT_MOST;
如果自己測(cè)量模式(即就是父布局)是 AT_MOST丽柿,即使子孩子大小是match_parent恢准,就給子孩子的測(cè)量模式 AT_MOST;
如果自己測(cè)量模式(即就是父布局)是 AT_MOST甫题,并且子孩子的大小是wrap_content馁筐,就給子孩子的測(cè)量模式 AT_MOST;

在最后會(huì)把獲取到的測(cè)量模式和大小坠非,即就是resultSize, resultMode返回回去
即就是返回到了measureChildWithMargins()方法中敏沉,如下圖所示:


圖片.png

返回這個(gè)測(cè)量模式和大小后,這個(gè)時(shí)候我們都會(huì)調(diào)用 setMeasuredDimesion()方法,這個(gè)時(shí)候我們的布局赦抖,才真正的指定了寬度和高度

/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/3/24 9:44
 * Version 1.0
 * Params:
 * Description:    測(cè)量模式計(jì)算方式
*/
public class TextView extends View {
    public TextView(Context context) {
        super(context);
    }

    public TextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // 指定寬高
        // widthMeasureSpec = childWidthMeasureSpec
        // heightMeasureSpec = childHeightMeasureSpec

        // 我們之前講的
        // wrap_content = AT_MOST
        // match_parent 舱卡、fill_parent辅肾、100dp = Exactly

        // 測(cè)量模式和大小是由父布局和它的孩子決定的队萤,比方說(shuō):

        // 父布局是包裹內(nèi)容,就算子布局是match_parent矫钓,這個(gè)是時(shí)候的測(cè)量模式還是 AT_MOST
        // 父布局是match_parent要尔,就算子布局是match_parent,這個(gè)時(shí)候的測(cè)量模式是 EXACTLY

        setMeasuredDimension();
    }
}

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);
    }
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }
而setMeasuredDimension();方法其實(shí)什么都沒(méi)干新娜,就是在setMeasuredDimensionRaw()方法中給寬高賦值赵辕,在這個(gè)時(shí)候 mMeasuredWidth和mMeasureHeight才真正的有值
然后測(cè)量所有子孩子的寬高,源碼中是通過(guò)for循環(huán)概龄,獲取所有子孩子还惠,然后去調(diào)用child.measure(childWidthMeasureSpec, childHeightMeasureSpec)方法測(cè)量子View的高度,即就是TextView的高度私杜,等測(cè)量出TextView的寬高蚕键,然后去測(cè)量自己的寬高(即就是父布局):
如果自己(即就是父布局)是LinearLayout并且是垂直方向,那么自己高度就是不斷的疊加子View的高度衰粹; childHeight = child.getMeasuredHeight();
如果自己(即就是父布局)是RelativeLayout锣光,那么父布局的高度是,指定子孩子中最高的铝耻;
<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:id="@+id/text_view" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            android:id="@+id/text_view2" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            android:id="@+id/text_view3" />
    </LinearLayout>

總結(jié):

測(cè)量是從外往里遞歸誊爹,也就是說(shuō):
ViewRootImpl會(huì)把自己的測(cè)量模式傳遞給 -> DecorView,然后DecorView會(huì)把自己的測(cè)量模式傳遞給 activity_main中的LinearLayout ->
然后LinearLayout通過(guò)for循環(huán)把自己的測(cè)量模式傳遞給 TextView瓢捉,然后就會(huì)調(diào)用 TextView的onMeasure()方法频丘,然后根據(jù)傳遞過(guò)來(lái)的LinearLayout的測(cè)量模式來(lái)指定 TextView的寬高,測(cè)量完畢后通過(guò)childHeight = child.getMeasuredHeight();獲取到 子View的寬高泡态,即就是獲取到3個(gè)TextView的寬高后椎镣,來(lái)計(jì)算父布局,即就是LinearLayout
自己的寬高 兽赁,然后再把自己的寬高向外傳遞給DecorView ->
然后DecorView根據(jù) LinearLayout傳回來(lái)的寬高状答,然后計(jì)算自己的寬高 , 把自己寬高計(jì)算好后,然后再把自己的寬高向外傳遞給 ViewRootImpl 刀崖。


onMeasure()源碼分析總結(jié)如下:
測(cè)量是從外往里遞歸的:
從外往里:

首先從最外層的 ViewRootImpl開(kāi)始惊科,它把它的 測(cè)量模式傳遞給 DecorView,然后DecorView把自己的測(cè)量模式 傳遞給 父布局LinearLayout亮钦,然后LinearLayout再把自己的測(cè)量模式 傳遞給 子View馆截;

從里往外:

等子View計(jì)算出自己寬高后,然后把自己寬高傳遞給父布局LinearLayout,然后LinearLayout根據(jù) 子View的寬高蜡娶,來(lái)計(jì)算自己的寬高混卵,這里計(jì)算方式就是:
如果父布局是 LinearLayout,且是垂直方向窖张,父布局高度就是累加子布局高度幕随;
如果父布局是RelativeLayout,那么父布局高度就是指定 子孩子中最高的宿接;
赘淮,然后LinearLayout把自己的高度傳遞給 它的父布局,就是這樣一路都把自己的高度傳遞給父布局睦霎,最后傳遞給 DecorView梢卸、傳遞給 ViewRootImpl,

onMeasure()源碼中就是這樣測(cè)量的副女,如下圖所示:
onMeasure()源碼分析.png

代碼已上傳至github:
https://github.com/shuai999/View_day08_2

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蛤高,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碑幅,更是在濱河造成了極大的恐慌戴陡,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枕赵,死亡現(xiàn)場(chǎng)離奇詭異猜欺,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)拷窜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)开皿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人篮昧,你說(shuō)我怎么就攤上這事赋荆。” “怎么了懊昨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵窄潭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我酵颁,道長(zhǎng)嫉你,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任躏惋,我火速辦了婚禮幽污,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘簿姨。我一直安慰自己距误,他們只是感情好簸搞,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著准潭,像睡著了一般趁俊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上刑然,一...
    開(kāi)封第一講書(shū)人閱讀 52,736評(píng)論 1 312
  • 那天寺擂,我揣著相機(jī)與錄音,去河邊找鬼闰集。 笑死沽讹,一個(gè)胖子當(dāng)著我的面吹牛般卑,可吹牛的內(nèi)容都是我干的武鲁。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蝠检,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼沐鼠!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起叹谁,我...
    開(kāi)封第一講書(shū)人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤饲梭,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后焰檩,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體憔涉,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年析苫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了兜叨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衩侥,死狀恐怖国旷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情茫死,我是刑警寧澤跪但,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站峦萎,受9級(jí)特大地震影響屡久,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜爱榔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一被环、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧搓蚪,春花似錦蛤售、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)揣钦。三九已至,卻和暖如春漠酿,著一層夾襖步出監(jiān)牢的瞬間冯凹,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工炒嘲, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宇姚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓夫凸,卻偏偏與公主長(zhǎng)得像浑劳,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子夭拌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

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