一莹痢、需要了解的知識(shí)
-
DecorView
DecroView 其實(shí)是一個(gè) FrameLayout,它包含了一個(gè)垂直的 LinerLayout峡懈,這個(gè) LinerLayout 包含手機(jī)通知欄滑沧、 titlebar 標(biāo)題欄,下部分為內(nèi)容欄也就是 Activity 通過(guò) setContentView 設(shè)置的布局嘉蕾。
內(nèi)容欄的 id 為 content,布局加到了 id 為 content 的 FrameLayout 中霜旧。
獲取 content 內(nèi)容欄:
ViewGroup content = findViewById(R.android.id.content);
獲取設(shè)置的布局 View:
content.getChildAt(0);
ViewRoot 和 ViewRootImpl
ViewRoot 是連接 WindowManager 和 DecorView 的紐帶错忱,View 的三大流程均是通過(guò) ViewRoot 來(lái)完成的。然而 ViewRoot 不是 View颁糟,它的實(shí)現(xiàn)類(lèi) ViewRootImpl 跟 View 也沒(méi)有什么直接的關(guān)系航背。詳情可閱Android里那些令人費(fèi)解的命名(一)ViewRootMeasureSpec
MeasureSpec 是 View 的一個(gè) static 類(lèi),這個(gè)詞像兩個(gè)單詞的組成棱貌,翻譯為“測(cè)量規(guī)格”或“測(cè)量參數(shù)”玖媚。
MeasureSpec 代表一個(gè) 32 位的 int 值,它包含了 View 的大谢橥选(低30位代表 SpecSize)和測(cè)量模式(高2位代表 SpecMode)今魔。MeasureSpec 將這兩個(gè)值打包在了一起并提供了打包和解包的方法。
簡(jiǎn)單地說(shuō)障贸,MeasureSpec 代表了某個(gè) View 的測(cè)量大小和顯示模式错森。
MeasureSpec一共有三種模式:
UPSPECIFIED : 父容器不對(duì) View 有任何限制,要多大給多大篮洁,一般用于表示一種數(shù)值未定的測(cè)量狀態(tài)涩维;
EXACTLY:父容器已經(jīng)檢測(cè)或計(jì)算出 View 所需的大小,這是一個(gè)比較精確的數(shù)值袁波,一般最終 View 的大小就是這里指定的 SpecSize瓦阐;
AT_MOST:父容器指定了一個(gè)可用大小,View 的大小不能超過(guò)這個(gè)值篷牌。
二睡蟋、從源頭尋找 View 繪制流程
2.1 DecorView 的創(chuàng)建和測(cè)量
1.創(chuàng)建 ViewRootImpl 對(duì)象并調(diào)用 performTraversals 開(kāi)始繪制
ActivityThread 中,當(dāng) Activity 對(duì)象創(chuàng)建完畢枷颊,會(huì)將 DecorView 添加到 Window 中戳杀,同時(shí)創(chuàng)建 ViewRootImpl 對(duì)象该面,并將 ViewRootImp 對(duì)象和 DecorView 建立關(guān)聯(lián)。
// 創(chuàng)建 ViewRootImpl 對(duì)象
root = new ViewRootImpl(view.getContext(), display);
// 和 DecorView 建立關(guān)聯(lián)
root.setView(view, wparams, panelParentView);
從 ViewRoot 的 performTraversals
方法開(kāi)始信卡,將 View 按照 measure隔缀、layout、和 draw 的過(guò)程繪制出來(lái)坐求。performTraversals
方法會(huì)依次調(diào)用 performMeasure蚕泽、performLayout、performDraw 來(lái)完成 DecroView 的繪制流程桥嗤。
private void performTraversals() {
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
// performMeasure 執(zhí)行測(cè)量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
WindowManager.LayoutParams lp = mWindowAttributes;
if (didLayout) {
// performLayout 執(zhí)行 Layout,確定坐標(biāo)
performLayout(lp, mWidth, mHeight);
...
}
...
// performDraw 執(zhí)行繪制
performDraw();
...
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
/**
* 基于它的布局參數(shù)仔蝌,計(jì)算出窗口中根視圖的度量標(biāo)準(zhǔn)泛领。
* 也就是根據(jù)屏幕寬高和 DecorView 布局參數(shù)來(lái)確定 DecorView 的 MeasureSpec
*
* @param windowSize
* 窗口的可用寬度或高度
*
* @param rootDimension
* 窗口的一維(寬度或高度)的布局參數(shù)。
*
* @return 用于度量根視圖的度量指標(biāo)敛惊。
*/
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 = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.EXACTLY);
break;
...
}
return measureSpec;
}
ViewGroup:performMeasure --> measure --> onMeasure 在這個(gè)過(guò)程中渊鞋,onMeasure 會(huì)對(duì)所有子元素進(jìn)行 measure 。
ViewGroup:performLayout --> layout --> onLayout 然后對(duì)所有子元素進(jìn)行 layout
ViewGroup:performDraw --> draw --> onDraw 傳遞過(guò)程通過(guò) dispatchDraw
來(lái)完成
2. 開(kāi)始測(cè)量 DecorView
performMeasure 中其實(shí)調(diào)用了 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
方法瞧挤,mView 可以看作是上文 ViewRootImpl 對(duì)象設(shè)置進(jìn)來(lái)的 DecorView 的實(shí)例锡宋。接下來(lái)看一下 DecorView 的繼承關(guān)系:
DecorView extends FrameLayout extends ViewGroup extends View
DecorView、FrameLayout 還有 ViewGroup 最終都繼承了 View類(lèi)特恬,F(xiàn)rameLayout 和 ViewGroup 都沒(méi)有 measure 方法执俩,所以 mView.measure 最終會(huì)調(diào)用 View 類(lèi)的 measure 方法。
measure 方法是 View 類(lèi)的一個(gè) final 方法癌刽,不可重寫(xiě)役首。DecorView 調(diào)用了 measure 方法,接下來(lái)看一下 View 調(diào)用 measure 方法做了什么显拜。
View 類(lèi) measure 方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec,heightMeasureSpec);
..
}
插播 1:View 中 onMeasure
所做的事情:
measure 方法調(diào)用了 onMeasure 方法衡奥,所以具體 View 的測(cè)量工作是在 onMeasure 方法中進(jìn)行的:
View 類(lèi) onMeasure 方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasuredDimension
方法可以簡(jiǎn)單理解為給 View 的 mMeasuredWidth,和 mMeasuredWidth 設(shè)置值远荠,也就是確定 View 的測(cè)量寬高矮固。這里的測(cè)量寬高是通過(guò)傳遞過(guò)來(lái)的父 View 的 MeasureSpec 和子 View 的默認(rèn)屬性共同決定的,getDefaultSize 方法會(huì)證明這一點(diǎn)譬淳。設(shè)置完成后就代表當(dāng)前 View 的寬/高測(cè)量值確定了档址。
那么 View 的測(cè)量寬高是通過(guò) getDefaultSize
方法返回的參數(shù)來(lái)確定具體的測(cè)量值。
getDefaultSize
方法傳入兩個(gè)參數(shù)
- 第一個(gè)參數(shù)為
getSuggestedMinimumWidth
方法的返回值:
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
根據(jù)該方法的返回值來(lái)看瘦赫,如果當(dāng)前 View 沒(méi)有設(shè)置背景辰晕,則返回的值為 View 的 android:minWidth 屬性所設(shè)置的值,可以為0确虱。如果指定了背景含友,則返回的值為 android:minWidth 屬性值和背景的最小寬度兩者中的最大值。對(duì)于高度,邏輯也是一樣的窘问,android:minHeight 屬性和背景共同決定辆童。
- 第二個(gè)參數(shù)為
measure
方法中傳遞過(guò)來(lái)的寬/高的 MeasureSpec 格式的測(cè)量值,是由父 View 的MeasureSpec 和子 View 自己的 LayoutParams 共同決定的惠赫,后文再作分析把鉴。
View 類(lèi) getDefaultSize 方法
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = View.MeasureSpec.getMode(measureSpec);
int specSize = View.MeasureSpec.getSize(measureSpec);
// 根據(jù)父 View 傳遞來(lái)的 SpecMode 來(lái)決定返回 View 的測(cè)量值
switch (specMode) {
case View.MeasureSpec.UNSPECIFIED:
result = size;
break;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
上面已經(jīng)分析過(guò)這兩個(gè)參數(shù),getDefaultSize
默認(rèn)返回的是傳遞來(lái)的第一個(gè)參數(shù)儿咱,也就是由最小寬高和背景寬高共同決定的值庭砍。接下來(lái)進(jìn)入 switch 判斷,如果傳遞來(lái)的 SpecMode 為 UNSPECIFIED混埠,則返回由 minHeight/minWidth 和背景共同決定的值怠缸。如果是 AT_MOST(規(guī)定上限)或 EXACTLY(精確值)模式,則返回由父 View 的MeasureSpec 和子 View 自己的 LayoutParams 共同決定的測(cè)量參數(shù)的值钳宪,也就是第二個(gè)參數(shù)中的 SpecSize揭北。
3. 遞歸測(cè)量 DecorView 的子 View,最后測(cè)量 DecorView
View 的 measure 方法會(huì)調(diào)用 onMeasure 來(lái)做具體的測(cè)量工作吏颖。DecorView 其實(shí)是一個(gè) FrameLayout 且重寫(xiě)了 onMeasure 方法搔体,所以接下來(lái)會(huì)進(jìn)入 FrameLayout 的 onMeasure
方法。接著會(huì)調(diào)用父類(lèi) ViewGroup 的 measureChildWithMargins 方法來(lái)測(cè)量所有子 View半醉。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// 遍歷子 View 測(cè)量它們的值
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
// 不為 GONE 的子 View 都會(huì)參與測(cè)量
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 父類(lèi) ViewGroup 中的方法疚俱,用來(lái)測(cè)量所有子 View
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
...
}
}
...
// 父類(lèi) View 中的方法,用來(lái)確定當(dāng)前 View 的測(cè)量值(當(dāng)前是 FrameLayout)
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
...
}
插播 2:ViewGroup 中 measureChildWithMargins
所做的事情:
ViewGroup 類(lèi) measureChildWithMargins 方法源碼
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
// child.getLayoutParams()獲取的是子 View 在 xml 中所設(shè)置的布局參數(shù)
final ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
// 根據(jù)父 View 的測(cè)量參數(shù) parentWidthMeasureSpec/parentHeightMeasureSpec
// 以及子 View 的布局參數(shù) Margin奉呛、Padding 和 width/height 來(lái)確定子 View 的測(cè)量參數(shù)
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);
// 算出寬和高的測(cè)量參數(shù)后计螺,讓子 View 繼續(xù)測(cè)量。
// 如果子 View 是 ViewGroup 則會(huì)遞歸往下級(jí) View 測(cè)量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
簡(jiǎn)單理解:首先瞧壮,拿到子 View 的 xml 設(shè)置的布局參數(shù)登馒,父 View 的測(cè)量參數(shù)(MeasureSpec)是由父 View 的 onMeasure
傳遞過(guò)來(lái)的。接著咆槽,將這些參數(shù)傳遞到 getChildMeasureSpec
方法來(lái)計(jì)算出子 VIew 的測(cè)量參數(shù)陈轿。最后子 View 調(diào)用 measure
方法來(lái)繼續(xù)測(cè)量,如果該子 View 是 ViewGroup秦忿,會(huì)繼續(xù)調(diào)用 measureChildWithMargins
來(lái)遞歸測(cè)量下級(jí) View麦射,如果子 View 只剩一個(gè)單獨(dú)的 View,則調(diào)用 measure
之后再調(diào)用
onMeasure
中的 setMeasuredDimension
來(lái)完成測(cè)量灯谣。
接下來(lái)看 getChildMeasureSpec
方法來(lái)計(jì)算出子 VIew 的測(cè)量參數(shù)的過(guò)程:
ViewGroup 類(lèi) getChildMeasureSpec 方法源碼
/**
* @param spec 父 View 傳遞來(lái)的測(cè)量參數(shù)
* @param padding 上面方法傳遞來(lái)的父View的padding和子View的margin以及widthUsed(已使用的寬度/高度)
* lp 是子View的布局參數(shù)
* mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed
* @param childDimension 子View布局參數(shù)設(shè)定的寬或高
* @return 子View的布局參數(shù)
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = View.MeasureSpec.getMode(spec); // 父View的SpecMode
int specSize = View.MeasureSpec.getSize(spec); // 父View的SpecSize
// 具體大小為父View的大小 減去 父View的padding和子View的margin以及widthUsed
// 暫時(shí)稱(chēng)之為 View剩余大小
int size = Math.max(0, specSize - padding);
// 用來(lái)生成子View的測(cè)量參數(shù)
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// 父View的大小是EXACTLY模式
case View.MeasureSpec.EXACTLY:
if (childDimension >= 0) { // 子View布局參數(shù)寬/高大于等于0
resultSize = childDimension; // 子View的寬/高為子View設(shè)置的布局參數(shù)值
resultMode = View.MeasureSpec.EXACTLY; // 子View的模式為精確模式
} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) { // 子View是MATCH_PARENT
resultSize = size; // 讓子View的數(shù)值為計(jì)算好的View剩余大小
resultMode = View.MeasureSpec.EXACTLY; // 子View的模式為精確模式
} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) { // 子View是WRAP_CONTENT
resultSize = size; // 子View的數(shù)值為計(jì)算好的View剩余大小
resultMode = View.MeasureSpec.AT_MOST; // 子View的模式為AT_MOST潜秋,表示不能超過(guò)這個(gè)View剩余大小
}
break;
// 父View的大小是AT_MOST模式
case View.MeasureSpec.AT_MOST:
if (childDimension >= 0) { // 子View布局參數(shù)寬/高大于等于0
resultSize = childDimension; // 子View的寬/高為子View設(shè)置的布局參數(shù)值
resultMode = View.MeasureSpec.EXACTLY; // 子View的模式為精確模式
} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) { // 子View是MATCH_PARENT
resultSize = size; // 子View的數(shù)值為View剩余大小
resultMode = View.MeasureSpec.AT_MOST; // 因?yàn)楦竀iew是AT_MOST模式其數(shù)據(jù)不確定,
// 所以子View也是AT_MOST不能超過(guò)View剩余大小
} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {// 子View是WRAP_CONTENT
// 子View想自適應(yīng)胎许,但它最大不能超過(guò)View剩余大小
resultSize = size;
resultMode = View.MeasureSpec.AT_MOST;
}
break;
// 父View的大小是UNSPECIFIED模式
case View.MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {// 子View布局參數(shù)寬/高大于等于0
// 子View寬高是精確數(shù)值峻呛,父View不限制所以子View也不加限制
resultSize = childDimension;
resultMode = View.MeasureSpec.EXACTLY; //子View寬高是精確數(shù)值所以其模式也應(yīng)為EXACTLY
} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {// 子View是MATCH_PARENT
// 如果父View的大小未確定罗售,返回0,否則返回View剩余大小
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = View.MeasureSpec.UNSPECIFIED; // 父View大小無(wú)限制钩述,子View也應(yīng)是
} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
// 判斷模式與上一個(gè)類(lèi)似
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = View.MeasureSpec.UNSPECIFIED;
}
break;
}
// 根據(jù)上文子View的SpecMode和SpecSize來(lái)打包成一個(gè)MeasureSpec對(duì)象
return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
簡(jiǎn)單總結(jié):
首先拿到父 View 的 specMode 和 specSize寨躁,specSize是父 View 的測(cè)量寬/高。使用 specSize 減去 父 View 的 padding 和子 View 的 margin 以及widthUsed 來(lái)計(jì)算出一個(gè)剩余大小牙勘,也就是子 View 能拿來(lái)用作顯示的區(qū)域大小职恳,暫且稱(chēng)之為 View 剩余大小。
然后根據(jù)父 View 的 specMode 和子 View 具體的布局參數(shù)來(lái)確定子 View 的 specMode 和 specSize:
- 父 View 是EXACTLY模式方面,說(shuō)明父 View 的大小是精確的放钦,一般來(lái)說(shuō),這個(gè)大小就是最終展示到屏幕上的大小葡幸。
- 如果子 View 的大小在布局中設(shè)定好了最筒,并且大于等于 0,那么子 View 的值是確定的蔚叨,并且其模式為 EXACTLY。無(wú)論父 View 的大小與模式辙培。
- 如果子 View 的大小為 "match_parent"蔑水,當(dāng)父 View 精確大小,那么子 View 的大小為計(jì)算好的 View 剩余大小扬蕊,并且模式也是 EXACTLY搀别。
- 如果子 View 的大小為 "wrap_content",當(dāng)父 View 精確大小尾抑,子 View 再怎么自適應(yīng)也不應(yīng)超過(guò)父 View 剩余大小歇父,所以 specSize 為 View 剩余大小,specMode 為 AT_MOST再愈。
- 父 View 是AT_MOST模式榜苫,說(shuō)明父 View 的大小不確定,但是會(huì)小于某個(gè)固定值翎冲。
- 同樣垂睬,如果子 View 的大小在布局中設(shè)定好了,并且大于等于 0抗悍,那么子 View 的值是確定的驹饺,并且其模式為 EXACTLY。無(wú)論父 View 的大小與模式缴渊。
- 如果子 View 的大小為 "match_parent"赏壹,子 View 的模式為 AT_MOST,并且其最大值為 View 剩余大小衔沼。
- 邏輯同上蝌借,也應(yīng)是 AT_MOST 模式并且最大值為 View 剩余大小昔瞧。
- 父 View 是UNSPECIFIED模式,表面父 View 的大小沒(méi)有約束骨望,所以子 View 的大小也可以是沒(méi)有限制大小的硬爆。
- 同樣,如果子 View 的大小在布局中設(shè)定好了擎鸠,并且大于等于 0缀磕,那么子 View 的值是確定的,并且其模式為 EXACTLY劣光。
- 如果子 View 的大小為 "match_parent"袜蚕,子 View 的大小理所應(yīng)當(dāng)也是無(wú)限制模式的,并且如果父View的大小未確定绢涡,返回0牲剃,否則返回View剩余大小。
- 邏輯同上雄可。
(3)DecorView 作為 FrameLayout 調(diào)用 FrameLayout 的 onMeasure 方法凿傅,onMeasure 方法又調(diào)用了 ViewGroup 的 measureChildWithMargins 方法來(lái)測(cè)量所有子 View。子 View 測(cè)量完畢后数苫,接著調(diào)用父類(lèi) View 的 setMeasuredDimension 方法聪舒,這個(gè)方法在上面 View 的測(cè)量流程中有提到過(guò)。
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;
}
總之就是 DecorView 作為 FrameLayout 先測(cè)量子 View虐急,然后再確定自己的測(cè)量值箱残。關(guān)于子 View 是如何測(cè)量的,可以回去看第一條插播止吁。
2.2 Layout 過(guò)程
DecroView 完成 performMeasure 中的 measure 過(guò)程后被辑,就完成了 DecroView 的大小和繪制模式,接下來(lái)就會(huì)進(jìn)行 Layout 過(guò)程來(lái)放置 DecroView 的具體位置敬惦。然后判斷 didLayout (didLayout 由 Window 狀態(tài)等參數(shù)決定)盼理,如果為 true 則執(zhí)行 Layout 過(guò)程。
private void performTraversals() {
...
// performMeasure 執(zhí)行測(cè)量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
WindowManager.LayoutParams lp = mWindowAttributes;
if (didLayout) {
// performLayout 執(zhí)行 Layout仁热,確定坐標(biāo)
performLayout(lp, mWidth, mHeight);
...
}
...
}
ViewRootImp 中 performLayout 方法
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
// 賦值給 host 并判斷是否為 null
final View host = mView;
if (host == null) {
return;
}
...
// 進(jìn)行具體的 layout 過(guò)程
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
由上面代碼可知 DecorView 調(diào)用 layout 方法榜揖,這個(gè)方法被 ViewGroup 重寫(xiě)為 final 類(lèi)型(說(shuō)明所有繼承 ViewGroup 的 View 的 layout 過(guò)程被 ViewGroup 統(tǒng)一管理),DecorView 是一個(gè) FrameLayout 繼承了 ViewGroup抗蠢。所以會(huì)調(diào)用 ViewGroup 的 layout 方法举哟。
ViewGroup layout 方法
@Override
public final void layout(int l, int t, int r, int b) {
// mSuppressLayout 判斷當(dāng)前布局是否被父 ViewGroup 進(jìn)行增加或刪除的動(dòng)畫(huà)效果
// mTransition 判斷當(dāng)前布局是否存在動(dòng)畫(huà)或動(dòng)畫(huà)正在改變布局
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// transition 動(dòng)畫(huà)正在運(yùn)行設(shè)置為 true,等待動(dòng)畫(huà)完成重新調(diào)用 requestLayout() 重新請(qǐng)求 Layout 步驟
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
上述代碼可以簡(jiǎn)單理解為 layout 之前先進(jìn)行 Transition 動(dòng)畫(huà)的判定迅矛,如果有動(dòng)畫(huà)在執(zhí)行則設(shè)置 mLayoutCalledWhileSuppressed 為 true 作為標(biāo)記妨猩,等待動(dòng)畫(huà)完畢重新調(diào)用 layout。
如果Transition 沒(méi)有在執(zhí)行秽褒,則調(diào)用父類(lèi)的 layout(l, t, r, b);
方法壶硅,也就是 View 類(lèi)的 layout()
方法威兜。
View layout 方法
public void layout(int l, int t, int r, int b) {
...
// 先標(biāo)記舊的屬性值
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// 判斷 LayoutMode 是否為 LAYOUT_MODE_OPTICAL_BOUNDS 模式,如果是則執(zhí)行 setOpticalFrame庐椒。
// 不是則執(zhí)行 setFrame椒舵,這個(gè)函數(shù)的作用為:判斷當(dāng)前 View 的屬性是否發(fā)生變化,并返回是否有變化约谈。
// 同時(shí)為當(dāng)前 View 設(shè)置一個(gè)大小和位置笔宿。
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// 判斷當(dāng)前 View 是否發(fā)生變化,來(lái)決定是否需要重新 layout
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
// 判斷是否在圓形可穿戴設(shè)備上繪制圓角
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
...
}
...
}
- 拿到上下左右的屬性值并標(biāo)記棱诱,然后判斷屬性值是否發(fā)生變化并更新設(shè)置泼橘。
- 如果 View 發(fā)生了變化則回調(diào) onLayout() 來(lái)確定坐標(biāo),在初始情況下各個(gè)屬性都是默認(rèn)的迈勋。一旦子 View 完成 measure 過(guò)程炬灭,這些屬性都會(huì)被賦值,所以會(huì)回調(diào)具體類(lèi)的 onLayout() 方法靡菇。View 類(lèi)的 onLayout() 方法是空實(shí)現(xiàn)
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
由于 DecorView 繼承 FrameLayout 又繼承 ViewGroup 又繼承 View重归,所以倒著來(lái)看 :
ViewGroup 的 onLayout() 方法
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
簡(jiǎn)單粗暴的一個(gè)抽象方法,也就是說(shuō)需要子類(lèi)具體的去實(shí)現(xiàn)厦凤。
FrameLayout 的 onLayout() 方法
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
// 獲取父 View 的相關(guān)屬性
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
// 省略的是一些根據(jù)子 View 的 LayoutGravity 和父 View 的邊界屬性來(lái)重新計(jì)算的代碼
...
// 計(jì)算完畢之后子 View 再遞歸進(jìn)行 layout提前。根據(jù)具體的 View 回調(diào)相關(guān)的 onLayout 方法
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
- 到 layoutChildren 方法也是拿了一堆屬性 ,然后遍歷子 View 來(lái)balabala 進(jìn)行計(jì)算(省略了)最終確定子 View 的左右上下邊距再使用遞歸的方式來(lái)進(jìn)行子 View 的 layout 方法泳唠。
- 進(jìn)行 layout 會(huì)回調(diào) onLayout 方法,不同的 View 會(huì)重寫(xiě) onLayout 方法宙搬,具體可以去看不同 View 的源碼笨腥。
-
onLayout(boolean changed, int left, int top, int right, int bottom)
方法中的各種屬性在 measure 過(guò)程中計(jì)算好傳遞過(guò)來(lái)的,但是具體進(jìn)行 layout 過(guò)程可能會(huì)根據(jù)子 View 的 LayoutGravity 模式來(lái)進(jìn)行具體的更改勇垛。
至此脖母,Layout 過(guò)程告一段落。
2.3 Draw 過(guò)程
performLayout 執(zhí)行完畢之后會(huì)進(jìn)行 performDraw 方法來(lái)進(jìn)行繪制闲孤,
private void performTraversals() {
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
// performMeasure 執(zhí)行測(cè)量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
WindowManager.LayoutParams lp = mWindowAttributes;
if (didLayout) {
// performLayout 執(zhí)行 Layout谆级,確定坐標(biāo)
performLayout(lp, mWidth, mHeight);
...
}
...
// performDraw 執(zhí)行繪制
performDraw();
...
}
performDraw()
方法經(jīng)過(guò)層層調(diào)用會(huì)執(zhí)行mView.draw(canvas);
方法,這里的 mView 依舊是 DecorView 的實(shí)例讼积。因?yàn)閂iew的draw 方法一般不去重寫(xiě)肥照,官網(wǎng)文檔也建議不要去重寫(xiě) draw 方法,所以接下來(lái)進(jìn)入 View 類(lèi)的 draw 方法:
public void draw(Canvas canvas) {
...
/*
* 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;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
...
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
...
}
這塊的注釋寫(xiě)的非常清晰勤众,一條條列出來(lái)了繪制步驟舆绎,其中第 2 步和第 5 步是在特點(diǎn)情況下才會(huì)進(jìn)行的,所以分析時(shí)省略這兩塊的步驟:
- 繪制背景
private void drawBackground(Canvas canvas) {
Drawable final Drawable background = mBackground;
......
//mRight - mLeft, mBottom - mTop layout確定的四個(gè)點(diǎn)來(lái)設(shè)置背景的繪制區(qū)域
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false; rebuildOutline();
}
......
//調(diào)用Drawable的draw() 把背景圖片畫(huà)到畫(huà)布上
background.draw(canvas);
......
}
- 如有必要们颜,請(qǐng)保存畫(huà)布圖層以準(zhǔn)備淡入淡出
- 繪制View的內(nèi)容
View 的 onDraw() 和 ViewGroup 的 onDraw() 都是空實(shí)現(xiàn)吕朵,所以需要具體的子 View 去實(shí)現(xiàn)相應(yīng)的繪制功能 - 繪制View的子View們
View 的dispatchDraw(canvas);
方法也是一個(gè)空實(shí)現(xiàn)猎醇,ViewGroup 實(shí)現(xiàn)了這個(gè)方法,在 ViewGroup 中該函數(shù)遍歷所有子元素的 draw 方法努溃,這樣繪制事件就一層一層地傳遞了下去硫嘶。
ViewGroup 的 dispatchDraw 方法
@Override
protected void dispatchDraw(Canvas canvas) {
...
if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
} else {
for (int i = 0; i < count; i++) {
final View child = children[getChildDrawingOrder(count, i)];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}
......
}
drawChild 方法直接調(diào)用了 child.draw(canvas, this, drawingTime);
方法。又是在循環(huán)中梧税,所以會(huì)調(diào)用所有 child View 的 draw 方法沦疾。
- 如有必要,繪制漸變邊緣并恢復(fù)圖層
- 繪制裝飾(例如滾動(dòng)條)
- 繪制默認(rèn)焦點(diǎn)高亮
到這里 View 的繪制流程就基本結(jié)束了贡蓖。
參考資料:
Android View的繪制流程
《Android 開(kāi)發(fā)藝術(shù)探索》