一.概述
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 的心跳。
首先我們看一下 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(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的繪制流程——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)
通過上述代碼,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蜂挪。
五.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 的工作流程的大致整體已經(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(),減少開銷司浪。
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.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í)行知允。