Android View的三大流程

一.概述

Android中的任何一個布局资昧、任何一個控件其實都是直接或間接繼承自View實現(xiàn)的酬土,當(dāng)然也包括我們后面一步一步引出的自定義控件也不例外,所以說這些View應(yīng)該都具有相同的繪制流程與機(jī)制才能顯示到屏幕上格带,經(jīng)過總結(jié)發(fā)現(xiàn)每一個View的繪制過程都必須經(jīng)歷三個最主要的過程撤缴,也就是measure、layout和draw叽唱。

二.View的工作流程

整個View樹的繪圖流程是在Window添加過程中屈呕,ViewRootImpl類的setView方法開始的:

//ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { 
    // 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;//mLayoutRequested 是否measure和layout布局。 
        scheduleTraversals(); 
    } 
} 

void scheduleTraversals() { 
    if (!mTraversalScheduled) { 
        mTraversalScheduled = true; 
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); 
        //post一個runnable處理-->mTraversalRunnable 
        mChoreographer.postCallback( 
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 
        ... 
    } 
} 

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

final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); 

void doTraversal() { 
    mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); 
    performTraversals();//View的繪制流程正式開始尔觉。 
}

在 scheduleTraversals 方法中凉袱,通過 mHandler 發(fā)送一個 Runnable芥吟,在 run() 方法中去處理繪制流程侦铜,這一點和 ActivityThread 的 H類 相似专甩,因為我們知道 ViewRootImpl 中 W類 是 Binder 的 Native端,用來接收 WMS 處理操作钉稍,因為 W類 的接收方法是在線程池中的涤躲,所以我們可以通過 Handler 將事件處理切換到主線程中。

performTraversals()

ViewRootImpl 在其創(chuàng)建過程中通過 requestLayout() 向主線程發(fā)送了一條觸發(fā)遍歷操作的消息贡未,遍歷操作是指 performTraversals() 方法种樱。它是一個包羅萬象的方法。ViewRootImpl 中接收的各種變化俊卤,如來自 WMS 的窗口屬性變化嫩挤、來自控件樹的尺寸變化及重繪請求等都引發(fā) performTraversals() 的調(diào)用,并在其中完成處理消恍。View類 及其子類中的 onMeasure()岂昭、onLayout()、onDraw() 等回調(diào)也都是在 performTraversals() 的執(zhí)行過程中直接或間接的引發(fā)狠怨。也正是如此约啊,一次次的 performTraversals() 調(diào)用驅(qū)動著控件樹有條不紊的工作,一旦此方法無法正常執(zhí)行佣赖,整個控件樹都將處于僵死狀態(tài)恰矩。因此 performTraversals() 函數(shù)可以說是 ViewRootImpl 的心跳。

View_1.jpg

首先我們看一下 performTraversals() 首次繪制的大致流程憎蛤,如上圖所示:

performTraversals() 會一次調(diào)用 performMeasure外傅、performLayout、performDraw 三個方法俩檬,這三個方法便是View繪制流程的精髓所在栏豺。

performMeasure :

會調(diào)用 measure 方法,在 measure 方法中又會調(diào)用 onMeasure 方法豆胸,在 onMeasure 方法中則會對所有的子元素進(jìn)行 measure 過程奥洼,這個時候 measure 流程就從父容器傳到子元素中了,這樣就完成了一次 measure 過程晚胡。measure 完成以后灵奖,可以通過 getMeasuredWidth 和 getMeasureHeight 方法來獲取到 View 測量后的寬高。

performLayout :

和 performMeasure 同理估盘。Layout 過程決定了 View 的四個頂點的坐標(biāo)和實際 View 的寬高瓷患,完成以后,可以通過 getTop/Bottom/Left/Right 拿到 View 的四個頂點位置遣妥,并可以通過 getWidth 和 getHeight 方法來拿到 View 的最終寬高擅编。

performDraw :

和 performMeasure 同理,唯一不同的是,performDraw 的傳遞過程是在 draw 方法中通過 dispatchDraw 來實現(xiàn)的爱态。Draw 過程則決定了 View 的顯示谭贪,只有 draw 方法完成以后 View 的內(nèi)容才能呈現(xiàn)在屏幕上。

我們知道锦担,View 的繪制流程是從 頂級View 也就是 DecorView「ViewGroup」開始俭识,一層一層從 ViewGroup 至 子View 遍歷測繪,我們先縱觀 performTraversals() 全局洞渔,認(rèn)識 View 繪制的一個整體架構(gòu)套媚,后面我們會補(bǔ)充說明部分重要代碼。如 : view.invalidate磁椒、view.requestLayout堤瘤、view.post(runnable) 等。

//ViewRootImpl
private void performTraversals() { 
    //調(diào)用performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 
    measureHierarchy(host, lp, res,desiredWindowWidth, desiredWindowHeight); 
    performLayout(lp, mWidth, mHeight); 
    performDraw(); 
}

三.View中的幾個概念

MeasureSpec

View 的測量過程中浆熔,還需要理解 MeasureSpec宙橱,MeasureSpec 決定了一個 View 的尺寸規(guī)格,并且 View 的 MeasureSpec 受自身的 LayoutParams(一般是xml布局中 width 和 height)和父容器 MeasureSpec 的影響蘸拔。

MeasureSpec 代表一個 32位 的int值师郑,高2位代表 SpecMode,低30位代表 SpecSize调窍。

SpecMode : 測量模式宝冕,有UNSPECIFIED、 EXACTLY邓萨、AT_MOST三種地梨。

SpecSize : 在某種測量模式下的尺寸和大小。

SpecMode 有如下三種取值:

UNSPECIFIED : 父容器對 子View 的尺寸不作限制缔恳,通常用于系統(tǒng)內(nèi)部宝剖。(listView 和 scrollView 等)

EXACTLY : SpecSize 表示 View 的最終大小,因為父容器已經(jīng)檢測出 View 所需要的精確大小歉甚,它對應(yīng) LayoutParams 中的 match_parent 和具體的數(shù)值這兩種模式万细。

AT_MOST : SpecSize 表示父容器的可用大小,View 的大小不能大于這個值纸泄。它對應(yīng) LayoutParams 中的 wrap_content赖钞。

MeasureSpec和LayoutParams
View_2.jpg

對于普通 View(DecorView 略有不同),其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 共同決定聘裁,MeasureSpec 一旦確定后雪营,onMeasure 中就可以確定 View 的測量寬高。

那么針對不同父容器和 View 本身不同的 LayoutParams衡便,View就可以有多種 MeasureSpec献起,我們從 子View 的角度來分析洋访。

① View 寬/高采用 固定寬高 的時候,不管父容器的 MeasureSpec 是什么谴餐,View 的 MeasureSpec 都是 EXACTLY 并且其大小遵循 LayoutParams 中的大小姻政。

② View 寬/高采用 wrap_content 的時候 ,不管父容器的模式是 EXACTLY 還是 AT_MOST总寒,View 的模式總是 AT_MOST,并且大小不能超過父容器的剩余空間(SpecSize,可用大欣矸巍)摄闸。

③ View 寬/高采用 match_parent 的時候 :

如果父容器的模式是 EXACTLY,那么 View 也是 EXACTLY 并且其大小是父容器的剩余空間(SpecSize,最終大忻萌)年枕;

如果父容器的模式是 AT_MOST,那么 View 也是 AT_MOST 并且其大小不會超過父容器的剩余空間(SpecSize,可用大泻跬辍)熏兄。

View_3.jpg

四.View的繪制流程——measure

有了上面的理論知識鋪墊之后,我們來看一下 DecorView 的 measure 過程树姨。

performTraversals
private void performTraversals() { 
    final View host = mView; //mView為DecorView
    int desiredWindowWidth;//decorView寬度 
    int desiredWindowHeight;//decorView高度 

    if (mFirst) { 
        if (shouldUseDisplaySize(lp)) { 
            //窗口的類型中有狀態(tài)欄和摩桶,所以高度需要減去狀態(tài)欄 
            Point size = new Point(); 
            mDisplay.getRealSize(size); 
            desiredWindowWidth = size.x; 
            desiredWindowHeight = size.y; 
        } else { 
            //窗口的寬高即整個屏幕的寬高 
            Configuration config = mContext.getResources().getConfiguration(); 
            desiredWindowWidth = dipToPx(config.screenWidthDp); 
            desiredWindowHeight = dipToPx(config.screenHeightDp); 
        } 
        //在onCreate中view.post(runnable)和此方法有關(guān) 
        host.dispatchAttachedToWindow(mAttachInfo, 0); 
    } 

    boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw); 
    if (layoutRequested) { 
        ... 
        //創(chuàng)建了DecorView的MeasureSpec,并調(diào)用performMeasure 
         measureHierarchy(host, lp, res,desiredWindowWidth, desiredWindowHeight); 
    }

在 ViewRootImpl 中帽揪,我們?nèi)缟戏治鲆幌轮饕a硝清,在 measureHierarchy() 方法中,創(chuàng)建了 DecorView 的 MeasureSpec转晰。

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, 
        final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) { 
    int childWidthMeasureSpec; 
    int childHeightMeasureSpec; 
    boolean windowSizeMayChange = false; 
    boolean goodMeasure = false; 

    //針對設(shè)置WRAP_CONTENT的dialog芦拿,開始協(xié)商,縮小布局參數(shù) 
    if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) { 
        // On large screens, we don't want to allow dialogs to just 
        // stretch to fill the entire width of the screen to display 
        // one line of text.  First try doing the layout at a smaller 
        // size to see if it will fit.         
        int baseSize = 0; 
        if (mTmpValue.type == TypedValue.TYPE_DIMENSION) { 
            baseSize = (int)mTmpValue.getDimension(packageMetrics); 
        } 
            childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); 
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); 
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 
            goodMeasure = true; 
    } 
    ...... 

    if (!goodMeasure) {//DecorView,寬度基本都為match_parent 
        childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); 
        childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); 
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 
        if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) { 
            windowSizeMayChange = true; 
        } 
    } 
    return windowSizeMayChange; 
} 

//創(chuàng)建measureSpec private static int getRootMeasureSpec(int windowSize, int rootDimension) { 
    int measureSpec; 
    switch (rootDimension) { 
        case ViewGroup.LayoutParams.MATCH_PARENT: 
            // Window can't resize. Force root view to be windowSize. 
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); 
            break; 
        case ViewGroup.LayoutParams.WRAP_CONTENT: 
            // Window can resize. Set max size for root view. 
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); 
            break; 
        default: 
            // Window wants to be an exact size. Force root view to be that size. 
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); 
            break; 
    } 
    return measureSpec; 
}

measureHierarchy() 用于測量整個控件樹,目標(biāo)是創(chuàng)建DecorView的MeasureSpec查邢。傳入的參數(shù) desiredWindowWidth 和 desiredWindowHeight 在前面方法中根據(jù)當(dāng)前窗口的不同情況(狀態(tài)欄)挑選而出蔗崎,不過 measureHierarchy() 有自己的考量方法,讓窗口布局更優(yōu)雅扰藕,(針對 wrap_content 的 dialog)缓苛,所以設(shè)置了 wrap_content 的 Dialog,有可能執(zhí)行多次測量邓深。(DecorView 的xml布局中他嫡,寬高基本都為 match_parent)

View_4.jpg

通過上述代碼,DecorView 的 MeasureSpec 的產(chǎn)生過程就很明確了庐完,具體來說其遵守如下規(guī)則钢属,根據(jù)它的 LayoutParams 中的寬高的參數(shù)來劃分 : (與上面所說的 MeasureSpec 和 LayoutParams 同理)

LayoutParams.MATCH_PARENT : EXACTLY(精確模式),大小就是窗口的大忻徘淆党;

LayoutParams.WRAP_CONTENT : AT_MOST (最大模式),大小不確定,但是不能超過窗口的大小染乌,暫定為窗口大猩娇住;

固定大泻杀铩(寫死的值) : EXACTLY(精確模式)台颠,大小就是當(dāng)前寫死的數(shù)值。

performMeasure
//該方法很簡單勒庄,直接調(diào)用mView.measure()方法
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { 
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 
}

在 view.measure() 的方法里串前,僅當(dāng)給與的 MeasureSpec 發(fā)生變化時,或要求強(qiáng)制重新布局時实蔽,才會進(jìn)行測量荡碾。

強(qiáng)制重新布局 : 控件樹中的一個子控件內(nèi)容發(fā)生變化時,需要重新測量和布局的情況局装,在這種情況下坛吁,這個子控件的父控件(以及父控件的父控件)所提供的 MeasureSpec 必定與上次測量時的值相同,因而導(dǎo)致從 ViewRootImpl 到這個控件的路徑上铐尚,父控件的 measure() 方法無法得到執(zhí)行拨脉,進(jìn)而導(dǎo)致子控件無法重新測量其布局和尺寸。

解決途徑 : 因此宣增,當(dāng)子控件因內(nèi)容發(fā)生變化時女坑,從子控件到父控件回溯到 ViewRootImpl,并依次調(diào)用父控件的 requestLayout() 方法统舀。這個方法會在 mPrivateFlags 中加入標(biāo)記 PFLAG_FORCE_LAYOUT匆骗,從而使得這些父控件的 measure() 方法得以順利執(zhí)行,進(jìn)而這個子控件有機(jī)會進(jìn)行重新布局與測量誉简。這便是強(qiáng)制重新布局的意義所在碉就。

measure
//View
//final類,子類不能重寫該方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { 
    ...... 
    onMeasure(widthMeasureSpec, heightMeasureSpec); 
} 

//ViewGroup并沒有重寫該方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), 
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); 

}

view.measure() 方法其實沒有實現(xiàn)任何測量的算法闷串,它的作用在于判斷是否需要引發(fā) onMeasure() 的調(diào)用瓮钥,并對 onMeasure() 行為的正確性進(jìn)行檢查。

onMeasure

ViewGroup 是一個抽象類烹吵,并沒有重寫 onMeasure() 方法碉熄,就要具體實現(xiàn)類去實現(xiàn)該方法。因為我們的 頂級View 是 DecorView肋拔,是一個 FrameLayout锈津,所以我們從 FrameLayout 開始繼續(xù)我們的主線任務(wù)。

ViewGroup -> FrameLayout -> onMeasure()
//FrameLayout
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
    int count = getChildCount(); 
    int maxHeight = 0; 
    int maxWidth = 0; 
    int childState = 0; 

    for (int i = 0; i < count; i++) { 
        final View child = getChildAt(i); 
        //遍歷子View凉蜂,只要子View不是GONE便處理 
        if (mMeasureAllChildren || child.getVisibility() != GONE) { 
            //子View結(jié)合父View的MeasureSpec和自己的LayoutParams算出子View自己的MeasureSpec 
            //如果當(dāng)前child也是ViewGroup琼梆,也繼續(xù)遍歷它的子View 
            //如果當(dāng)前child是View性誉,便根據(jù)這個MeasureSpec測量自己 
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); 
           ...... 
        } 
    } 
    ...... 
    //父View等所有的子View測量結(jié)束之后,再來測量自己 
    setMeasuredDimension(......); 
}

我們知道 ViewGroup 的 measure 任務(wù)主要是測量所有的 子View茎杂,測量完畢之后根據(jù)合適的寬高再測量自己错览。

在 FrameLayout 的 onMeasure() 方法中,會通過 measureChildWithMargins() 方法遍歷 子View煌往,并且如果 FrameLayout 寬高的 MeasureSpec 是 AT_MOST倾哺,那么 FrameLayout 計算自身寬高就會受到 子View 的影響,可能使用最大 子View 的寬高刽脖。

不同 ViewGroup 實現(xiàn)類有不同的測量方式羞海,例如 LinearLayout 自身的高度可能是 子View 高度的累加。

measureChildWithMargins() 方法為 ViewGroup 提供的方法曾棕,根據(jù) 父View 的 MeasureSpec 和 子View 的 LayoutParams扣猫,算出 子View 自己的 MeasureSpec菜循。

measureChildWithMargins
//ViewGroup
protected void measureChildWithMargins(View child, 
        int parentWidthMeasureSpec, int widthUsed, 
        int parentHeightMeasureSpec, int heightUsed) { 
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 

    //根據(jù)View的LayoutParams,margin和父容器的MeasureSpec翘地,padding獲取View的MeasureSpec
    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); 
}

上述方法會對子元素進(jìn)行 measure,在調(diào)用子元素的 measure 方法之前會先通過 getChildMeasureSpec 方法來得到子元素的 MeasureSpec癌幕。顯然子元素的 MeasureSpec 的創(chuàng)建與父容器的 MeasureSpec 和子元素本身的 LayoutParams 有關(guān)衙耕,此外還和 View 的 margin 和 padding 有關(guān),如果對該理論知識印象不太深刻建議滑到上個段落 —- MeasureSpec 勺远,再來看以下代碼橙喘,事半功倍。

getChildMeasureSpec
//ViewGroup
//spec為父容器的MeasureSpec
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { 
    int specMode = MeasureSpec.getMode(spec);//父容器的specMode 
    int specSize = MeasureSpec.getSize(spec);//父容器的specSize 
    int size = Math.max(0, specSize - padding); 
    int resultSize = 0; 
    int resultMode = 0; 

    switch (specMode) {//根據(jù)父容器的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; 
    // Parent asked to see how big we want to be 
    case MeasureSpec.UNSPECIFIED: 
        if (childDimension >= 0) { 
            // Child wants a specific size... let him have it 
            resultSize = childDimension; 
            resultMode = MeasureSpec.EXACTLY; 
        } else if (childDimension == LayoutParams.MATCH_PARENT) { 
            // Child wants to be our size... find out how big it should be 
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; 
            resultMode = MeasureSpec.UNSPECIFIED; 
        } else if (childDimension == LayoutParams.WRAP_CONTENT) { 
            // Child wants to determine its own size.... find out how big it should be 
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; 
            resultMode = MeasureSpec.UNSPECIFIED; 
        } 
        break; 
    } 
    //noinspection ResourceType 
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 
}

上述方法不難理解胶逢,它的主要作用是根據(jù)父容器的 MeasureSpec 同時結(jié)合 View 本身的 LayoutParams 來確定子元素的 MeasureSpec厅瞎。

View -> onMeasure

回顧一下上面的 measure 段落,因為具體實現(xiàn)的 ViewGroup 會重寫 onMeasure()初坠,因為 ViewGroup 是一個抽象類和簸,其測量過程的 onMeasure() 方法需要具體實現(xiàn)類去實現(xiàn),這么做的原因在于不同的 ViewGroup 實現(xiàn)類有不同的布局特性碟刺,比如說 FrameLayout锁保,RelativeLayout,LinearLayout 都有不同的布局特性半沽,因此 ViewGroup 無法對 onMeasure() 做同一處理爽柒。

但是對于普通的 View 只需完成自身的測量工作即可,所以可以看到 View 的 onMeasure 方法很簡潔者填。

//View
//final類浩村,子類不能重寫該方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { 
    ...... 
    onMeasure(widthMeasureSpec, heightMeasureSpec); 
} 

//ViewGroup并沒有重寫該方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
    setMeasuredDimension( 
    getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), 
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); 
} 

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

從 getDefaultSize() 方法的實現(xiàn)來看,對于 AT_MOST 和 EXACTLY 這兩種情況 View 的寬高都由 specSize 決定占哟,也就是說如果我們直接繼承 View 的自定義控件需要重寫 onMeasure 方法并設(shè)置 wrap_content 時自身的大小穴亏,否則在布局中使用 wrap_content 就相當(dāng)于使用 match_parent蜂挪。


View_5.jpg

五.onLayout

子View 具體 layout 的位置都是相對于父容器而言的,View 的 layout 過程和 measure 同理嗓化,也是從 頂級View 開始棠涮,遞歸的完成整個空間樹的布局操作。

經(jīng)過前面的測量刺覆,控件樹中的控件對于自己的尺寸顯然已經(jīng)了然于胸严肪。而且父控件對于子控件的位置也有了眉目,所以經(jīng)過測量過程后谦屑,布局階段會把測量結(jié)果轉(zhuǎn)化為控件的實際位置與尺寸驳糯。控件的實際位置與尺寸由 View 的 mLeft氢橙,mTop酝枢,mRight,mBottom 等4個成員變量存儲的坐標(biāo)值來表示悍手。

并且需要注意的是: View 的 mLeft帘睦,mTop,mRight坦康,mBottom 這些坐標(biāo)值是以父控件左上角為坐標(biāo)原點進(jìn)行計算的竣付。倘若需要獲取控件在窗口坐標(biāo)系中的位置可以使用View.GetLocationWindow() 或者是 View.getRawX()/Y()。
//ViewRootImpl
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) { 
    final View host = mView; 
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); 
}
//ViewGroup
//盡管ViewGroup也重寫了layout方法
//但是本質(zhì)上還是會通過super.layout()調(diào)用View的layout()方法
@Override
public final void layout(int l, int t, int r, int b) { 
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) { 
        //如果無動畫滞欠,或者動畫未運(yùn)行 
        super.layout(l, t, r, b); 
    } else { 
        //等待動畫完成時再調(diào)用requestLayout() 
        mLayoutCalledWhileSuppressed = true; 
    } 
} 

//View 
public void layout(int l, int t, int r, int b) { 
    int oldL = mLeft; 
    int oldT = mTop; 
    int oldB = mBottom; 
    int oldR = mRight; 
    //如果布局有變化古胆,通過setFrame重新布局 
    boolean changed = isLayoutModeOptical(mParent) ? 
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); 
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { 
        //如果這是一個ViewGroup,還會遍歷子View的layout()方法 
        //如果是普通View筛璧,通知具體實現(xiàn)類布局變更通知 
        onLayout(changed, l, t, r, b); 
        //清除PFLAG_LAYOUT_REQUIRED標(biāo)記 
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; 
        ...... 
        //布局監(jiān)聽通知 
    } 
    //清除PFLAG_FORCE_LAYOUT標(biāo)記 
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; 
}

通過代碼可以看到盡管 ViewGroup 也重寫了 layout() 方法逸绎,但是本質(zhì)上還是會走 View 的 layout()。

在 View 的 layout() 方法里夭谤,首先通過 setFrame()(setOpticalFrame() 也走 setFrame())將 l棺牧、t、r沮翔、b 分別設(shè)置到 mLeft陨帆、mTop、mRight 和 mBottom采蚀,這樣就可以確定 子View 在父容器的位置了疲牵,上面也說過了,這些位置是相對父容器的榆鼠。

然后調(diào)用 onLayout() 方法纲爸,使具體實現(xiàn)類接收到布局變更通知。如果此類是 ViewGroup妆够,還會遍歷 子View 的 layout() 方法使其更新布局识啦。如果調(diào)用的是 onLayout() 方法负蚊,這會導(dǎo)致 子View 無法調(diào)用 setFrame(),從而無法更新控件坐標(biāo)信息颓哮。

//View
protected void onLayout(boolean changed, int l, int t, int r, int b) {} 

//ViewGroup
//abstract修飾家妆,具體實現(xiàn)類必須重寫該方法
@Override
protected abstract void onLayout(boolean changed,int l, int t, int r, int b);

對于普通 View 來說,onLayout() 方法是一個空實現(xiàn)冕茅,主要是具體實現(xiàn)類重寫該方法后能夠接收到布局坐標(biāo)更新信息伤极。

對于 ViewGroup 來說,和 measure 一樣姨伤,不同實現(xiàn)類有它不同的布局特性哨坪,在 ViewGroup 中 onLayout() 方法是 abstract 的,具體實現(xiàn)類必須重寫該方法乍楚,以便接收布局坐標(biāo)更新信息后胡岔,處理自己的 子View 的坐標(biāo)信息耐版。有興趣的童鞋可以看 FrameLayout 或者 LinearLayout 的 onLayout() 方法状植。

小結(jié)

對比測量 measure 和布局 layout 兩個過程有助于加深對它們的理解鸟妙。(摘自《深入理解Android卷III》)

measure 確定的是控件的尺寸唉匾,并在一定程度上確定了子控件的位置蠢壹。而布局則是針對測量結(jié)果來實施猫妙,并最終確定子控件的位置喧务。

measure 結(jié)果對布局過程沒有約束力串绩。雖說子控件在 onMeasure() 方法中計算出了自己應(yīng)有的尺寸缺虐,但是由于 layout() 方法是由父控件調(diào)用,因此控件的位置尺寸的最終決定權(quán)掌握在父控件手中礁凡,測量結(jié)果僅僅只是一個參考高氮。

因為 measure 過程是后根遍歷(DecorView 最后 setMeasureDiemension()),所以子控件的測量結(jié)果影響父控件的測量結(jié)果顷牌。

而 Layout 過程是先根遍歷(layout() 一開始就調(diào)用 setFrame() 完成 DecorView 的布局)剪芍,所以父控件的布局結(jié)果會影響子控件的布局結(jié)果。

完成 performLayout() 后窟蓝,空間樹的所有控件都已經(jīng)確定了其最終位置罪裹,就剩下繪制了。

六.draw

我們先純粹的看 View 的 draw 過程运挫,因為這個過程相對上面 measure 和 layout 比較簡單状共。

View 的 draw 過程遵循如下幾步 :

繪制背景 drawBackground();

繪制自己 onDraw();

如果是 ViewGroup 則繪制 子View,dispatchDraw();

繪制裝飾(滾動條)和前景谁帕,onDrawForeground();

//View
public void draw(Canvas canvas) { 
    final int privateFlags = mPrivateFlags; 
    //檢查是否是"實心(不透明)"控件峡继。(后面有補(bǔ)充) 
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && 
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); 
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; 
    /* 
     * Draw traversal performs several drawing steps which must be executed 
     * in the appropriate order: 
     * 
     *      1. Draw the background 
     *      2. If necessary, save the canvas' layers to prepare for fading 
     *      3. Draw view's content 
     *      4. Draw children 
     *      5. If necessary, draw the fading edges and restore layers 
     *      6. Draw decorations (scrollbars for instance) 
     */ 
    // Step 1, draw the background, if needed 
    int saveCount; 
    //非"實心"控件,將會繪制背景 
    if (!dirtyOpaque) { 
        drawBackground(canvas); 
    } 
    // skip step 2 & 5 if possible (common case) 
    final int viewFlags = mViewFlags; 
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; 
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; 
    //如果控件不需要繪制漸變邊界匈挖,則可以進(jìn)入簡便繪制流程 
    if (!verticalEdges && !horizontalEdges) { 
        // Step 3, draw the content 
        if (!dirtyOpaque) onDraw(canvas);//非"實心"碾牌,則繪制控件本身 
        // Step 4, draw the children 
        dispatchDraw(canvas);//如果當(dāng)前不是ViewGroup康愤,此方法則是空實現(xiàn) 
        // Overlay is part of the content and draws beneath Foreground 
        if (mOverlay != null && !mOverlay.isEmpty()) { 
            mOverlay.getOverlayView().dispatchDraw(canvas); 
        } 
        // Step 6, draw decorations (foreground, scrollbars) 
        onDrawForeground(canvas);//繪制裝飾和前景 
        // we're done... 
        return; 
    } 
    ...... 
}
View_6.jpg

至此 View 的工作流程的大致整體已經(jīng)描述完畢了。

七.invalidate

我們知道 invalidate() (在主線程)和 postInvalidate() (可以在子線程)都是用于請求 View 重繪的方法舶吗,那么它是如何實現(xiàn)的呢征冷?

invalidate() 方法必須在主線程執(zhí)行,而 scheduleTraversals() 引發(fā)的遍歷也是在主線程執(zhí)行誓琼。所以調(diào)用 invalidate() 方法并不會使得遍歷立即開始资盅,因為在調(diào)用 invalidate() 的方法執(zhí)行完畢之前(準(zhǔn)確的說是主線程的Looper處理完其他消息之前),主線程根本沒有機(jī)會處理 scheduleTraversals() 所發(fā)出的消息踊赠。

這種機(jī)制帶來的好處是 : 在一個方法里可以連續(xù)調(diào)用多個控件的 invalidate() 方法呵扛,而不用擔(dān)心會由于多次重繪而產(chǎn)生的效率問題。

另外多次調(diào)用 invalidate() 方法會使得 ViewRootImpl 多次接收到設(shè)置臟區(qū)域的請求筐带,ViewRootImpl 會將這些臟區(qū)域累加到 mDirty 中今穿,進(jìn)而在隨后的遍歷中,一次性的完成所有臟區(qū)域的重繪伦籍。

窗口第一次繪制時候蓝晒,ViewRootImpl 的 mFullRedrawNeeded 成員將會被設(shè)置為 true,也就是說 mDirty 所描述的區(qū)域?qū)U(kuò)大到整個窗口帖鸦,進(jìn)而實現(xiàn)完整重繪芝薇。

View的臟區(qū)域和”實心”控件

增加兩個知識點,能夠更好的理解 View 的重繪過程作儿。

為了保證繪制的效率洛二,控件樹僅對需要重繪的區(qū)域進(jìn)行繪制。這部分區(qū)域成為”臟區(qū)域” Dirty Area攻锰。

當(dāng)一個控件的內(nèi)容發(fā)生變化而需要重繪時晾嘶,它會通過 View.invalidate() 方法將其需要重繪的區(qū)域沿著控件樹自下而上的交給 ViewRootImpl,并保存在 ViewRootImpl 的 mDirty 成員中娶吞,最后通過 scheduleTraversals() 引發(fā)一次遍歷垒迂,進(jìn)而進(jìn)行重繪工作,這樣就可以保證僅位于 mDirty 所描述的區(qū)域得到重繪妒蛇,避免了不必要的開銷机断。

View的isOpaque() 方法返回值表示此控件是否為”實心”的,所謂”實心”控件绣夺,是指在 onDraw() 方法中能夠保證此控件的所有區(qū)域都會被其所繪制的內(nèi)容完全覆蓋吏奸。對于”實心”控件來說,背景和子元素(如果有的話)是被其 onDraw() 的內(nèi)容完全遮住的乐导,因此便可跳過遮擋內(nèi)容的繪制工作從而提升效率苦丁。

簡單來說透過此控件所屬的區(qū)域無法看到此控件下的內(nèi)容,也就是既沒有半透明也沒有空缺的部分物臂。因為自定義 ViewGroup 控件默認(rèn)是”實心”控件旺拉,所以默認(rèn)不會調(diào)用 drawBackground() 和 onDraw() 方法产上,因為一旦 ViewGroup 的 onDraw() 方法,那么就會覆蓋住它的子元素蛾狗。但是我們?nèi)匀豢梢酝ㄟ^調(diào)用 setWillNotDraw(false) 和 setBackground() 方法來開啟ViewGroup 的 onDraw() 功能晋涣。

下面我們從View的invalidate方法,自下(View)而上(ViewRootImpl)的分析沉桌。

invalidate : 使無效谢鹊; damage : 損毀;dirty : 臟留凭;

View
//View
public void invalidate() { 
    invalidate(true); 
} 

void invalidate(boolean invalidateCache) { 
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); 
} 

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, 
        boolean fullInvalidate) { 
    //如果VIew不可見佃扼,或者在動畫中 
    if (skipInvalidate()) { 
        return; 
    } 
    //根據(jù)mPrivateFlags來標(biāo)記是否重繪 
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) 
            || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) 
            || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED 
            || (fullInvalidate && isOpaque() != mLastIsOpaque)) { 
        if (fullInvalidate) {//上面?zhèn)魅霝閠rue,表示需要全部重繪 
            mLastIsOpaque = isOpaque();// 
            mPrivateFlags &= ~PFLAG_DRAWN;//去除繪制完畢標(biāo)記蔼夜。 
        } 
        //添加標(biāo)記兼耀,表示View正在繪制。PFLAG_DRAWN為繪制完畢求冷。 
        mPrivateFlags |= PFLAG_DIRTY; 
        //清除緩存瘤运,表示由當(dāng)前View發(fā)起的重繪。 
        if (invalidateCache) { 
            mPrivateFlags |= PFLAG_INVALIDATED; 
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; 
        } 
        //把需要重繪的區(qū)域傳遞給父View 
        final AttachInfo ai = mAttachInfo; 
        final ViewParent p = mParent; 
        if (p != null && ai != null && l < r && t < b) { 
            final Rect damage = ai.mTmpInvalRect; 
            //設(shè)置重繪區(qū)域(區(qū)域為當(dāng)前View在父容器中的整個布局) 
            damage.set(l, t, r, b); 
            //調(diào)用父容器的invalidateChild
            p.invalidateChild(this, damage); 
        } 
        ...... 
    } 
}

上述代碼中匠题,會設(shè)置一系列的標(biāo)記位到 mPrivateFlags 中拯坟,并且通過父容器的 invalidateChild 方法,將需要重繪的臟區(qū)域傳給父容器韭山。(ViewGroup 和 ViewRootImpl 都繼承了 ViewParent類郁季,該類中定義了子元素與父容器間的調(diào)用規(guī)范。)

ViewGroup(p.invalidateChild(this, damage); )
//ViewGroup
@Override 
public final void invalidateChild(View child, final Rect dirty) { 
    ViewParent parent = this; 
    final AttachInfo attachInfo = mAttachInfo; 
    if (attachInfo != null) { 
        RectF boundingRect = attachInfo.mTmpTransformRect; 
        boundingRect.set(dirty); 
        ......   
        //父容器根據(jù)自身對子View的臟區(qū)域進(jìn)行調(diào)整 
        transformMatrix.mapRect(boundingRect); 
        dirty.set((int) Math.floor(boundingRect.left), 
                (int) Math.floor(boundingRect.top), 
                (int) Math.ceil(boundingRect.right), 
                (int) Math.ceil(boundingRect.bottom)); 
        // 這里的do while方法掠哥,不斷的去調(diào)用父類的invalidateChildInParent方法來傳遞重繪請求 
        //直到調(diào)用到ViewRootImpl的invalidateChildInParent(責(zé)任鏈模式) 
        do { 
            View view = null; 
            if (parent instanceof View) { 
                view = (View) parent; 
            } 
            if (drawAnimation) { 
                if (view != null) { 
                    view.mPrivateFlags |= PFLAG_DRAW_ANIMATION; 
                } else if (parent instanceof ViewRootImpl) { 
                    ((ViewRootImpl) parent).mIsAnimating = true; 
                } 
            } 
            //如果父類是"實心"的巩踏,那么設(shè)置它的mPrivateFlags標(biāo)識 
            // If the parent is dirty opaque or not dirty, mark it dirty with the opaque 
            // flag coming from the child that initiated the invalidate 
            if (view != null) { 
                if ((view.mViewFlags & FADING_EDGE_MASK) != 0 && 
                        view.getSolidColor() == 0) { 
                    opaqueFlag = PFLAG_DIRTY; 
                } 
                if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) { 
                    view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag; 
                } 
            } 
            //***往上遞歸調(diào)用父類的invalidateChildInParent*** 
            parent = parent.invalidateChildInParent(location, dirty); 
            //設(shè)置父類的臟區(qū)域 
            //父容器會把子View的臟區(qū)域轉(zhuǎn)化為父容器中的坐標(biāo)區(qū)域 
            if (view != null) { 
                // Account for transform on current parent 
                Matrix m = view.getMatrix(); 
                if (!m.isIdentity()) { 
                    RectF boundingRect = attachInfo.mTmpTransformRect; 
                    boundingRect.set(dirty); 
                    m.mapRect(boundingRect); 
                    dirty.set((int) Math.floor(boundingRect.left), 
                            (int) Math.floor(boundingRect.top), 
                            (int) Math.ceil(boundingRect.right), 
                            (int) Math.ceil(boundingRect.bottom)); 
                } 
            } 
        }  
        while (parent != null); 
    } 
}
ViewRootImpl

我們先驗證一下最上層 ViewParent 為什么是 ViewRootImpl

//ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { 
    view.assignParent(this); 
} 

//View
void assignParent(ViewParent parent) { 
    if (mParent == null) { 
        mParent = parent; 
    } else if (parent == null) { 
        mParent = null; 
    } else { 
        throw new RuntimeException("view " + this + " being added, but" 
                + " it already has a parent"); 
    } 
}

在 ViewRootImpl 的 setView 方法中秃诵,由于傳入的 View 正是 DecorView续搀,所以最頂層的 ViewParent 即 ViewRootImpl。另外 ViewGroup 在 addView 方法中菠净,也會調(diào)用 assignParent() 方法禁舷,設(shè)定子元素的父容器為它本身。

由于最上層的 ViewParent 是 ViewRootImpl毅往,所以我們可以查看 ViewRootImpl 的 invalidateChildInParent 方法即可牵咙。

//ViewRootImpl
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) { 
    //檢查線程,這也是為什么invalidate一定要在主線程的原因 
    checkThread(); 
    if (dirty == null) { 
        invalidate();//有可能需要繪制整個窗口 
        return null; 
    } else if (dirty.isEmpty() && !mIsAnimating) { 
        return null; 
    } 
    ..... 
    invalidateRectOnScreen(dirty); 
    return null; 
} 

//設(shè)置mDirty并執(zhí)行View的工作流程 
private void invalidateRectOnScreen(Rect dirty) { 
    final Rect localDirty = mDirty; 
    if (!localDirty.isEmpty() && !localDirty.contains(dirty)) { 
        mAttachInfo.mSetIgnoreDirtyState = true; 
        mAttachInfo.mIgnoreDirtyState = true; 
    } 
    // Add the new dirty rect to the current one 
    localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);     
    //在這里攀唯,mDirty的區(qū)域就變?yōu)榉椒ㄖ械膁irty洁桌,即要重繪的臟區(qū)域 
    ...... 
    if (!mWillDrawSoon && (intersected || mIsAnimating)) { 
        scheduleTraversals();//執(zhí)行View的工作流程 
    } 
}

什么?執(zhí)行 invalidate() 方法居然會引起 scheduleTraversals()侯嘀!

那么也就是說 invalidate() 會導(dǎo)致 perforMeasure()另凌、performLayout()谱轨、perforDraw() 的調(diào)用了?吠谢?土童?

這個 scheduleTraversals() 很眼熟,我們一出場就在 requestLayout() 中見過工坊,并且我們還說了

mLayoutRequested 用來表示是否 measure 和 layout献汗。
//ViewRootImpl
@Override
public void requestLayout() { 
    if (!mHandlingLayoutInLayoutRequest) { 
        checkThread();//檢查是否在主線程 
        mLayoutRequested = true;//mLayoutRequested 是否measure和layout布局。 
        scheduleTraversals(); 
    } 
} 

private void performTraversals() { 
    boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw); 
    if (layoutRequested) { 
        measureHierarchy(```);//measure 
    } 

    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw); 
    if (didLayout) { 
        performLayout(lp, mWidth, mHeight);//layout 
    } 

    boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible; 
    if (!cancelDraw && !newSurface) { 
        performDraw();//draw 
    } 
}

因為我們 invalidate 的時候王污,并沒有設(shè)置 mLayoutRequested罢吃,所以放心,它只走 performDraw() 流程昭齐,并且在 draw() 流程中會清除 mDirty 區(qū)域刃麸。

并且只有設(shè)置了標(biāo)識為的 View 才會調(diào)用 draw 方法進(jìn)而調(diào)用 onDraw(),減少開銷司浪。


View_7.jpg
requestLayout

看完了 invalidate() 流程之后泊业,requestLayout() 流程就比較好上手了。我們在 measure 階段提到過 :

在 view.measure() 的方法里啊易,僅當(dāng)給與的 MeasureSpec 發(fā)生變化時吁伺,或要求強(qiáng)制重新布局時,才會進(jìn)行測量租谈。

強(qiáng)制重新布局 : 控件樹中的一個子控件內(nèi)容發(fā)生變化時篮奄,需要重新測量和布局的情況,在這種情況下割去,這個子控件的父控件(以及父控件的父控件)所提供的 MeasureSpec 必定與上次測量時的值相同窟却,因而導(dǎo)致從 ViewRootImpl 到這個控件的路徑上,父控件的 measure() 方法無法得到執(zhí)行呻逆,進(jìn)而導(dǎo)致子控件無法重新測量其布局和尺寸夸赫。(在父容器 measure 中遍歷子元素)

解決途徑 : 因此,當(dāng)子控件因內(nèi)容發(fā)生變化時咖城,從子控件到父控件回溯到 ViewRootImpl茬腿,并依次調(diào)用父控件的 requestLayout() 方法。這個方法會在 mPrivateFlags 中加入標(biāo)記 PFLAG_FORCE_LAYOUT宜雀,從而使得這些父控件的 measure() 方法得以順利執(zhí)行切平,進(jìn)而這個子控件有機(jī)會進(jìn)行重新布局與測量。這便是強(qiáng)制重新布局的意義所在辐董。

下面我們看 View 的 requestLayout() 方法

//View
@CallSuper
public void requestLayout() { 
    if (mMeasureCache != null) mMeasureCache.clear(); 
    ...... 
    // 增加PFLAG_FORCE_LAYOUT標(biāo)記悴品,在measure時會校驗此屬性 
    mPrivateFlags |= PFLAG_FORCE_LAYOUT; 
    mPrivateFlags |= PFLAG_INVALIDATED; 
    // 父類不為空&&父類沒有請求重新布局(是否有PFLAG_FORCE_LAYOUT標(biāo)志) 
    //這樣同一個父容器的多個子View同時調(diào)用requestLayout()就不會增加開銷 
    if (mParent != null && !mParent.isLayoutRequested()) { 
        mParent.requestLayout(); 
    } 
}

因為上面說過了,最頂層的 ViewParent 是 ViewRootImpl。

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

同樣苔严,requestLayout() 方法會調(diào)用 scheduleTraversals();菇存,因為設(shè)置了 mLayoutRequested =true 標(biāo)識,所以在 performTraversals() 中調(diào)用 performMeasure()邦蜜,performLayout()依鸥,但是由于沒有設(shè)置 mDirty,所以不會走 performDraw() 流程悼沈。

但是贱迟,requestLayout() 方法就一定不會導(dǎo)致 onDraw() 的調(diào)用嗎?

在上面 layout() 方法中說道 :

在 View 的 layout() 方法里絮供,首先通過 setFrame()(setOpticalFrame() 也走 setFrame())將 l衣吠、t、r壤靶、b 分別設(shè)置到 mLeft缚俏、mTop、mRight 和 mBottom贮乳,這樣就可以確定 子View 在父容器的位置了忧换,上面也說過了,這些位置是相對父容器的向拆。

//View --> layout()
protected boolean setFrame(int left, int top, int right, int bottom) { 
    boolean changed = false; 
    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) { 
        //布局坐標(biāo)改變了 
        changed = true; 
        int oldWidth = mRight - mLeft; 
        int oldHeight = mBottom - mTop; 
        int newWidth = right - left; 
        int newHeight = bottom - top; 
        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight); 
        // Invalidate our old position 
        invalidate(sizeChanged);//調(diào)用invalidate重新繪制視圖 
        if (sizeChanged) { 
            sizeChange(newWidth, newHeight, oldWidth, oldHeight); 
        } 
        ...... 
    } 
    return changed; 
}
看完代碼我們就很清晰的知道亚茬,如果 layout 布局有變化,那么它也會調(diào)用 invalidate() 重繪自身浓恳。
View_8.jpg

八.View.post()

我們知道調(diào)用這個方法可以保證在UI線程中進(jìn)行需要的操作刹缝,方便地進(jìn)行異步通信。以下是官方文檔對該方法的注釋及源碼颈将。(postDelayed類似梢夯,不再贅述)

Causes the Runnable to be added to the message queue.The runnable will be run on the user interface thread.
public boolean post(Runnable action) {    
  final AttachInfo attachInfo = mAttachInfo;
  if (attachInfo != null) {       
  return attachInfo.mHandler.post(action);
  }    
// Assume that post will succeed later       
  ViewRootImpl.getRunQueue().post(action); 
  return true;
}

我們使用 View.post() 時,其實內(nèi)部它自己分了兩種情況處理晴圾,當(dāng) View 還沒有 attachedToWindow 時颂砸,通過 View.post(Runnable) 傳進(jìn)來的 Runnable 操作都先被緩存在 HandlerActionQueue,然后等 View 的 dispatchAttachedToWindow() 被調(diào)用時疑务,就通過 mAttachInfo.mHandler 來執(zhí)行這些被緩存起來的 Runnable 操作沾凄。dispatchAttachedToWindow() 也是發(fā)了一個post,等View的流程走完了才會執(zhí)行知允。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市叙谨,隨后出現(xiàn)的幾起案子温鸽,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涤垫,死亡現(xiàn)場離奇詭異姑尺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蝠猬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門切蟋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人榆芦,你說我怎么就攤上這事柄粹。” “怎么了匆绣?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵驻右,是天一觀的道長。 經(jīng)常有香客問我崎淳,道長堪夭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任拣凹,我火速辦了婚禮森爽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嚣镜。我一直安慰自己拗秘,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布祈惶。 她就那樣靜靜地躺著雕旨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捧请。 梳的紋絲不亂的頭發(fā)上凡涩,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音疹蛉,去河邊找鬼活箕。 笑死,一個胖子當(dāng)著我的面吹牛可款,可吹牛的內(nèi)容都是我干的育韩。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼闺鲸,長吁一口氣:“原來是場噩夢啊……” “哼筋讨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起摸恍,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤悉罕,失蹤者是張志新(化名)和其女友劉穎赤屋,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壁袄,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡类早,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嗜逻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涩僻。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖栈顷,靈堂內(nèi)的尸體忽然破棺而出逆日,到底是詐尸還是另有隱情,我是刑警寧澤妨蛹,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布屏富,位于F島的核電站,受9級特大地震影響蛙卤,放射性物質(zhì)發(fā)生泄漏狠半。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一颤难、第九天 我趴在偏房一處隱蔽的房頂上張望神年。 院中可真熱鬧,春花似錦行嗤、人聲如沸已日。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽飘千。三九已至,卻和暖如春栈雳,著一層夾襖步出監(jiān)牢的瞬間护奈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工哥纫, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留霉旗,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓蛀骇,卻偏偏與公主長得像厌秒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子擅憔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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