簡(jiǎn)介
我們知道阳啥,在 Android 中趴拧,View 繪制主要包含 3 大流程:
measure(測(cè)量):主要用于確定 View 的測(cè)量寬/高紧唱。
layout(布局):主要用于確定 View 在父容器中的放置位置肘交。
draw(繪制):結(jié)合前面兩步結(jié)果氯窍,將 View 真正繪制到屏幕上缔杉。
Android 中锤躁,主要有兩種視圖:View和ViewGroup,其中:
View:就是一個(gè)獨(dú)立的視圖
ViewGroup:一個(gè)容器組件或详,該容器可容納多個(gè)子視圖系羞,即ViewGroup可容納多個(gè)View或ViewGroup,且支持嵌套霸琴。
雖然ViewGroup繼承于View椒振,但是在 View 繪制三大流程中,某些流程需要區(qū)分View和ViewGroup梧乘,它們之間的操作并不完全相同澎迎,比如:
View和ViewGroup都需要進(jìn)行 measure,確定各自的測(cè)量寬/高选调。View只需直接測(cè)量自身即可夹供,而ViewGroup通常都必須先測(cè)量所有子View,最后才能測(cè)量自己
通常ViewGroup先定位自己的位置(layout)仁堪,然后再定位其子View 位置(onLayout)
View需要進(jìn)行 draw 過程哮洽,而ViewGroup通常不需要(當(dāng)然也可以進(jìn)行繪制),因?yàn)閂iewGroup更多作為容器存在弦聂,起存儲(chǔ)放置功能
measure 流程
對(duì) View 進(jìn)行測(cè)量鸟辅,主要包含兩個(gè)步驟:
求取 View 的測(cè)量規(guī)格MeasureSpec。
依據(jù)上一步求得的MeasureSpec莺葫,對(duì) View 進(jìn)行測(cè)量剔桨,求取得到 View 的最終測(cè)量寬/高。
MeasureSpec
對(duì)于第一個(gè)步驟徙融,即求取 View 的MeasureSpec洒缀,首先我們來看下MeasureSpec的源碼定義:
// frameworks/base/core/java/android/view/View.java
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
// 生成測(cè)量規(guī)格
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
// 獲取測(cè)量模式
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
// 獲取測(cè)量大小
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
...
}
MeasureSpec是View的一個(gè)公有靜態(tài)內(nèi)部類,它是一個(gè) 32 位的int值,高 2 位表示 SpecMode(測(cè)量模式)树绩,低 30 位表示 SpecSize(測(cè)量尺寸/測(cè)量大腥浴)。
MeasureSpec將兩個(gè)數(shù)據(jù)打包到一個(gè)int值上饺饭,可以減少對(duì)象內(nèi)存分配渤早,并且其提供了相應(yīng)的工具方法可以很方便地讓我們從一個(gè)int值中抽取出 View 的 SpecMode 和 SpecSize。
一個(gè)MeasureSpec表達(dá)的是:該 View 在該種測(cè)量模式(SpecMode)下對(duì)應(yīng)的測(cè)量尺寸(SpecSize)瘫俊。其中鹊杖,SpecMode 有三種類型:
UNSPECIFIED:表示父容器對(duì)子View 未施加任何限制,子View 尺寸想多大就多大扛芽。
EXACTLY:如果子View 的模式為EXACTLY骂蓖,則表示子View 已設(shè)置了確切的測(cè)量尺寸,或者父容器已檢測(cè)出子View 所需要的確切大小川尖。
這種模式對(duì)應(yīng)于LayoutParams.MATCH_PARENT和子View 設(shè)置具體數(shù)值兩種情況登下。
AT_MOST:表示自適應(yīng)內(nèi)容,在該種模式下叮喳,View 的最大尺寸不能超過父容器的 SpecSize被芳,因此也稱這種模式為 最大值模式。
這種模式對(duì)應(yīng)于LayoutParams.WRAP_CONTENT馍悟。
LayoutParams
對(duì) View 進(jìn)行測(cè)量畔濒,最關(guān)鍵的一步就是計(jì)算得到 View 的MeasureSpec
,子View 在創(chuàng)建時(shí)锣咒,可以指定不同的LayoutParams
(布局參數(shù))侵状,LayoutParams
的源碼主要內(nèi)容如下所示:
// frameworks/base/core/java/android/view/ViewGroup.java
public static class LayoutParams {
...
/**
* Special value for the height or width requested by a View.
* MATCH_PARENT means that the view wants to be as big as its parent,
* minus the parent's padding, if any. Introduced in API Level 8.
*/
public static final int MATCH_PARENT = -1;
/**
* Special value for the height or width requested by a View.
* WRAP_CONTENT means that the view wants to be just large enough to fit
* its own internal content, taking its own padding into account.
*/
public static final int WRAP_CONTENT = -2;
/**
* Information about how wide the view wants to be. Can be one of the
* constants FILL_PARENT (replaced by MATCH_PARENT
* in API Level 8) or WRAP_CONTENT, or an exact size.
*/
public int width;
/**
* Information about how tall the view wants to be. Can be one of the
* constants FILL_PARENT (replaced by MATCH_PARENT
* in API Level 8) or WRAP_CONTENT, or an exact size.
*/
public int height;
...
}
其中:
-
LayoutParams.MATCH_PARENT
:表示子View 的尺寸與父容器一樣大(注:需要減去父容器padding
部分空間,讓父容器padding
生效) -
LayoutParams.WRAP_CONTENT
:表示子View 的尺寸自適應(yīng)其內(nèi)容大谐韬濉(注:需要包含子View 本身的padding
空間) -
width
/height
:表示 View 的設(shè)置寬/高壹将,即layout_width
和layout_height
設(shè)置的值,其值有三種選擇:LayoutParams.MATCH_PARENT
毛嫉、LayoutParams.WRAP_CONTENT
和 具體數(shù)值诽俯。
LayoutParams
會(huì)受到父容器的MeasureSpec
的影響,測(cè)量過程會(huì)依據(jù)兩者之間的相互約束最終生成子View 的MeasureSpec
承粤,完成 View 的測(cè)量規(guī)格暴区。
簡(jiǎn)而言之,View 的MeasureSpec
受自身的LayoutParams
和父容器的MeasureSpec
共同決定(DecorView
的MeasureSpec
是由自身的LayoutParams
和屏幕尺寸共同決定辛臊,參考后文)仙粱。也因此,如果要求取子View 的MeasureSpec
彻舰,那么首先就需要知道父容器的MeasureSpec
伐割,層層逆推而上候味,即最終就是需要知道頂層View(即DecorView
)的MeasureSpec
,這樣才能一層層傳遞下來隔心,這整個(gè)過程需要結(jié)合Activity
的啟動(dòng)過程進(jìn)行分析白群。
Activity 視圖基本結(jié)構(gòu)
我們知道,在 Android 中硬霍,Activity
是作為視圖組件存在帜慢,主要就是在手機(jī)上顯示視圖界面,可以供用戶操作唯卖,Activity
就是 Andorid 中與用戶直接交互最多的系統(tǒng)組件粱玲。
Activity
的基本視圖層次結(jié)構(gòu)如下所示:
Activity
中,實(shí)際承載視圖的組件是Window
(更具體來說為PhoneWindow
)拜轨,頂層View 是DecorView
抽减,它是一個(gè)FrameLayout
,DecorView
內(nèi)部是一個(gè)LinearLayout
撩轰,該LinearLayout
由兩部分組成(不同 Android 版本或主題稍有差異):TitleView
和ContentView
胯甩,其中昧廷,TitleView
就是標(biāo)題欄堪嫂,也就是我們常說的TitleBar
或ActionBar
,ContentView
就是內(nèi)容欄木柬,它也是一個(gè)FrameLayout
皆串,主要用于承載我們的自定義根布局,即當(dāng)我們調(diào)用setContentView(...)
時(shí)眉枕,其實(shí)就是把我們自定義的布局設(shè)置到該ContentView
中恶复。
當(dāng)Activity
啟動(dòng)完成后,最終就會(huì)渲染出上述層次結(jié)構(gòu)的視圖速挑。
DecorView 測(cè)量規(guī)格
因此谤牡,如果我們要求取得到子View 的MeasureSpec
,那么第一步就是求取得到頂層View(即DecorView
)的MeasureSpec
姥宝。大致過程如下所示:
-
在
Activity
啟動(dòng)過程中翅萤,會(huì)調(diào)用到ActivityThread.handleResumeActivity(...)
,該方法就是 View 視圖繪制的起始之處:// frameworks/base/core/java/android/app/ActivityThread.java final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { ... ActivityClientRecord r = performResumeActivity(token, clearHide); ... // 此處的 window 為與 Activity 綁定的 PhoneWindow腊满,即 Activity.mWindow r.window = r.activity.getWindow(); // PhoneWindow 綁定的頂層視圖:DecorView View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); // 獲取與 Activity 綁定的 WindowManager套么,實(shí)際上是 PhoneWindow 的 WindowManager ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); ... // 添加 DecorView 到 PhoneWindow 上(相當(dāng)于設(shè)置 Activity 根視圖) wm.addView(decor, l); ... }
其中,
r.window.getDecorView()
實(shí)際調(diào)用的是PhoneWindow.getDecorView()
碳蛋,其會(huì)返回頂層DecorView
(不存在時(shí)會(huì)自動(dòng)實(shí)例化):// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java public class PhoneWindow extends Window implements MenuBuilder.Callback { // This is the top-level view of the window, containing the window decor. private DecorView mDecor; ... @Override public final View getDecorView() { if (mDecor == null) { installDecor(); } return mDecor; } private void installDecor() { if (mDecor == null) { mDecor = generateDecor(); ... } ... } protected DecorView generateDecor() { // 實(shí)例化 DecorView return new DecorView(getContext(), -1); } ... }
然后胚泌,
r.window.getAttributes()
實(shí)際調(diào)用的是Window.getAttributes()
:// frameworks/base/core/java/android/view/Window.java public abstract class Window { private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams(); ... public final WindowManager.LayoutParams getAttributes() { return mWindowAttributes; } } // frameworks/base/core/java/android/view/WindowManager.java public interface WindowManager extends ViewManager { ... public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { public LayoutParams() { // DecorView 的布局參數(shù)為 MATCH_PARENT super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); ... } } }
這里可以看到,此處
r.window.getAttributes()
返回的是一個(gè)WindowManager.LayoutParams
實(shí)例肃弟,對(duì)應(yīng)的最終寬/高布局參數(shù)為LayoutParams.MATCH_PARENT
玷室,最后通過wm.addView(decor,l)
將DecorView
添加到WindowManager
上(最終其實(shí)是設(shè)置到ViewRootImpl
上)零蓉,所以DecorView
的布局參數(shù)為MATCH_PARENT
。 -
View 的繪制流程真正開始的地方為
ViewRootImpl.performTraversals()
穷缤,在其中壁公,有如下代碼片段:// frameworks/base/core/java/android/view/ViewRootImpl.java private void performTraversals() { ... int desiredWindowWidth; int desiredWindowHeight; ... // Ask host how big it wants to be windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight); ... } private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) { ... childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... }
此處的
desiredWindowWidth
和desiredWindowHeight
是屏幕的尺寸,內(nèi)部最終會(huì)調(diào)用到ViewRootImpl.getRootMeasureSpec(...)
绅项,其源碼如下所示:// frameworks/base/core/java/android/view/ViewRootImpl.java 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; }
ViewRootImpl.getRootMeasureSpec(...)
見名知意紊册,其實(shí)就是用來獲取頂層View(即DecorView
)的MeasureSpec
,其邏輯如下:- 當(dāng)
DecorView
的LayoutParams
為MATCH_PARENT
時(shí)快耿,說明DecorView
的大小與屏幕一樣大囊陡,而又由于屏幕大小是確定的,因此掀亥,其 SpecMode 為EXACTLY
撞反,SpecSize 為windowSize
,搪花; - 當(dāng)
DecorView
的LayoutParams
為WRAP_CONTENT
時(shí)遏片,說明DecorView
自適應(yīng)內(nèi)容大小,因此它的大小不確定撮竿,但是最大不能超過屏幕大小吮便,故其 SpecMode 為AT_MOST
,SpecSize 為windowSize
幢踏; - 其余情況為
DecorView
設(shè)置了具體數(shù)值大小或UNSPECIFIED
髓需,故以DecorView
為主,其 SpecMode 為EXACTLY
房蝉,SpecSize 就是自己設(shè)置的值僚匆,即rootDimension
;
結(jié)合我們上面的分析搭幻,由于
DecorView
的LayoutParams
為MATCH_PARENT
咧擂,因此,DecorView
的MeasureSpec
最終為:MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY)
檀蹋,即DecorView
的 SpecMode 為EXACTLY
松申,SpecSize 為屏幕大小。 - 當(dāng)
默認(rèn)測(cè)量(measure)
經(jīng)過上述步驟求取得到 View 的MeasureSpec
后续扔,接下來就可以真正對(duì) View 進(jìn)行測(cè)量攻臀,求取 View 的最終測(cè)量寬/高:
Android 內(nèi)部對(duì)視圖進(jìn)行測(cè)量的過程是由View#measure(int, int)
方法負(fù)責(zé)的,但是對(duì)于View
和ViewGroup
纱昧,其具體測(cè)量過程有所差異刨啸。
因此,對(duì)于測(cè)量過程识脆,我們分別對(duì)View
和ViewGroup
進(jìn)行分析:
-
View
測(cè)量:View
的測(cè)量過程由View.measure(...)
方法負(fù)責(zé)设联,其源碼如下所示:// frameworks/base/core/java/android/view/View.java public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); ... }
View#measure(int, int)
中參數(shù)widthMeasureSpec
和heightMeasureSpec
是由父容器傳遞進(jìn)來的善已,具體的測(cè)量過程請(qǐng)參考后文內(nèi)容。需要注意的是离例,
View#measure(int, int)
是一個(gè)final
方法换团,因此其不可被覆寫,實(shí)際真正測(cè)量 View 自身使用的是View#onMeasure(int, int)
方法宫蛆,如下所示:// frameworks/base/core/java/android/view/View.java protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
onMeasure(...)
主要做了三件事:-
首先通過
getSuggestedMinimumWidth()
/getSuggestedMinimumHeight()
方法獲取得到 View 的推薦最小測(cè)量寬/高:// frameworks/base/core/java/android/view/View.java protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); } protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }
這兩個(gè)方法的實(shí)現(xiàn)原理是一致的艘包,這里就只分析
getSuggestedMinimumWidth()
方法實(shí)現(xiàn),該方法內(nèi)部是一個(gè)三目運(yùn)算符耀盗,可以很清晰看出想虎,當(dāng) View 沒有設(shè)置背景時(shí),它的寬度就為mMinWidth
叛拷,mMinWidth
就是android:minWidth
這個(gè)屬性對(duì)應(yīng)設(shè)置的值(未設(shè)置android:minWidth
時(shí)舌厨,其值默認(rèn)為0
),當(dāng) View 設(shè)置了背景時(shí)忿薇,它的寬度就是mMinWidth
和mBackground.getMinimumWidth()
之中的較大值裙椭,其中,mBackground.getMinimumWidth()
源碼如下:// frameworks/base/graphics/java/android/graphics/drawable/Drawable.java /* * @return The minimum width suggested by this Drawable. If this Drawable * doesn't have a suggested minimum width, 0 is returned. */ public int getMinimumWidth() { final int intrinsicWidth = getIntrinsicWidth(); return intrinsicWidth > 0 ? intrinsicWidth : 0; } // 不同子類可實(shí)現(xiàn)具體大小 public int getIntrinsicWidth() { return -1; }
Drawable.getMinimumWidth()
就是返回 Drawable 的原始寬度署浩,如果該 Drawable 未設(shè)置寬度揉燃,則返回0
。綜上瑰抵,
getSuggestedMinimumWidth()
/getSuggestedMinimumHeight()
其實(shí)就是用于獲取 View 的最小測(cè)量寬/高你雌,其具體邏輯為:當(dāng) View 沒有設(shè)置背景時(shí)器联,其最小寬/高為android:minWidth
/android:mMinHeight
所指定的值二汛,當(dāng) View 設(shè)置了背景時(shí),其最小測(cè)量寬/高為android:minWidth
/android:minHeight
與其背景圖片寬/高的較大值拨拓。簡(jiǎn)而言之肴颊,View 的最小測(cè)量寬/高為
android:minWidth
/android:minHeight
和其背景寬/高之間的較大值。 -
通過
getDefaultSize(...)
獲取到 View 的默認(rèn)測(cè)量寬/高渣磷,具體獲取過程如下所示:// frameworks/base/core/java/android/view/View.java public static int getDefaultSize(int size, int measureSpec) { int result = size; // 測(cè)量模式 int specMode = MeasureSpec.getMode(measureSpec); // 測(cè)量大小 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; }
此處的
size
是通過getSuggestedMinimumWidth()
/getSuggestedMinimumHeight()
方法獲取得到系統(tǒng)建議 View 的最小測(cè)量寬/高婿着。參數(shù)
measureSpec
是經(jīng)由View.measure(...)
->View.onMeasure(...)
->View.getDefaultSize(...)
調(diào)用鏈傳遞進(jìn)來的,表示的是當(dāng)前 View 的MeasureSpec
醋界。getDefaultSize(...)
內(nèi)部首先會(huì)獲取 View 的測(cè)量模式和測(cè)量大小竟宋,然后當(dāng) View 的測(cè)量模式為UNSPECIFIED
時(shí),也即未限制 View 的大小形纺,因此此時(shí) View 的大小就是其原生大星鹣馈(也即android:minWidth
或背景圖片大小)逐样,當(dāng) View 的測(cè)量模式為AT_MOST
或EXACTLY
時(shí)蜗字,此時(shí)不對(duì)這兩種模式進(jìn)行區(qū)分打肝,一律將 View 的大小設(shè)置為測(cè)量大小(即 SpecSize)挪捕。
注:實(shí)際上粗梭,這里可以看到,默認(rèn)情況下级零,View 不區(qū)分AT_MOST
和EXACTLY
措嵌,也即,當(dāng)自定義 View 時(shí)耗美,LayoutParams.WRAP_CONTENT
和LayoutParams.MATCH_PARENT
效果是一樣的鸟顺,均為MATCH_PARENT
的效果,原因是 子View 的MeasureSpec
是由父容器傳遞進(jìn)來的亥贸,父容器是通過ViewGroup#getChildMeasureSpec(...)
方法獲取得到 子View 的MeasureSpec
躬窜,在該方法內(nèi)部,子View 的測(cè)量模式無論是AT_MOST
或是EXACTLY
炕置,其測(cè)量大小都為父容器大腥侔ぁ(確定的說,是父容器剩余空間大衅犹)默垄,因此其效果就等同于MATCH_PARENT
,具體源碼詳情分析請(qǐng)參考后文甚纲。總之口锭,一般自定義 View 時(shí),都需要覆寫
onMeasure(...)
介杆,并為其LayoutParams.WRAP_CONTENT
設(shè)置一個(gè)默認(rèn)大小鹃操,如下所示:@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 先進(jìn)行默認(rèn)測(cè)量 super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 默認(rèn)大小依據(jù)自己靈活配置,這里為 400px int defaultSize = 400; // 獲取默認(rèn)測(cè)量寬/高 int width = this.getMeasuredWidth(); int height = this.getMeasuredHeight(); // 獲取 View 的布局參數(shù) ViewGroup.LayoutParams lp = this.getLayoutParams(); // 寬度為自適應(yīng)春哨,則設(shè)置一個(gè)默認(rèn)大小 if(lp.width == ViewGroup.LayoutParams.WARP_CONTENT) { width = defaultSize; } // 高度為自適應(yīng)荆隘,則設(shè)置一個(gè)默認(rèn)大小 if(lp.height == ViewGroup.LayoutParams.WARP_CONTENT) { height = defaultSize; } this.setMeasuredDimension(width, height); }
-
獲取到 View 的測(cè)量寬/高后,通過
setMeasuredDimension(...)
記錄 View 的測(cè)量寬/高:// frameworks/base/core/java/android/view/View.java protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { ... setMeasuredDimensionRaw(measuredWidth, measuredHeight); } // 記錄測(cè)量寬/高 private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }
setMeasuredDimension(...)
其實(shí)就是將 View 的最終測(cè)量寬/高設(shè)置到View.mMeasuredWidth
/View.mMeasuredHeight
屬性中赴背,完成測(cè)量過程椰拒。
-
-
ViewGroup
測(cè)量:ViewGroup
是一個(gè)抽象類,其繼承于View
:public abstract class ViewGroup extends View implements ViewParent, ViewManager {...}
ViewGroup
的測(cè)量過程也是由View.measure(...)
負(fù)責(zé)凰荚,因此實(shí)際負(fù)責(zé)測(cè)量的是ViewGroup.onMeasure(...)
方法燃观,但是由于ViewGroup
的作用是用于容納子View,如果想測(cè)量ViewGroup
便瑟,則必須先測(cè)量其子View缆毁,而又由于不同的ViewGroup
有不同的布局特性,因此無法抽象出一套標(biāo)準(zhǔn)的測(cè)量流程胳徽,所以ViewGroup
本身沒有覆寫onMeasure(...)
方法(交由具體自定義ViewGroup
覆寫)积锅,但是它提供了一些測(cè)量子View 的輔助方法爽彤,比如:measureChildren(...)
、measureChildrenWithMargins(...)
缚陷、measureChild(...)
适篙、getChildMeasureSpec(...)
等等,自定義ViewGroup
可借助這些輔助方法箫爷,在onMeasure(...)
中完成子View 的測(cè)量嚷节,然后最終才能完成自己的測(cè)量。我們隨便選擇一個(gè)輔助方法虎锚,比如
ViewGroup#measureChildWithMargins(...)
硫痰,查看其源碼:// android/view/ViewGroup.java protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { // 獲取 子View 的 LayoutParams final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); // 獲取 子View 的 MeasureSpec // 父容器已使用的空間為:自身已使用空間 + 自身的 padding + 子View的 margin 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è)量子View child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
代碼非常簡(jiǎn)潔易懂,其核心就是先獲取得到 子View 的
MeasureSpec
(getChildMeasureSpec(...)
)窜护,然后就可以對(duì) 子View 進(jìn)行測(cè)量(child.measure(...)
)效斑。View#measure(...)
的測(cè)量詳情上述我們已經(jīng)介紹過了,這里我們主要來看下ViewGroup#getChildMeasureSpec(...)
獲取 子View 測(cè)量規(guī)格的具體過程:// android/view/ViewGroup.java /** * * @param spec 父容器的 MeasureSpec * @param padding 父容器已使用的空間(比如:父View自身的 padding + 子View的 margin) * @param childDimension 子View的 LayoutParams * @return 子View 的 MeasureSpec */ public static int getChildMeasureSpec(int spec, int padding, int childDimension) { // 當(dāng)前View(即父容器)的測(cè)量模式 int specMode = MeasureSpec.getMode(spec); // 父容器的測(cè)量大小 int specSize = MeasureSpec.getSize(spec); // 父容器剩余可用空間 int size = Math.max(0, specSize - padding); // 子View 最終測(cè)量大小 int resultSize = 0; // 子View 最終測(cè)量模式 int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us // 父容器大小已確定 case MeasureSpec.EXACTLY: if (childDimension >= 0) { // 子View 設(shè)置了具體大兄恪(精確數(shù)值) resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // 子View 大小撐滿父容器 // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // 子View 自適應(yīng)內(nèi)容大小 // 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 // 父容器自適應(yīng)內(nèi)容大小 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; } // 子View 的最終測(cè)量規(guī)格 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
getChildMeasureSpec(...)
其實(shí)就是ViewGroup
對(duì)其內(nèi)部 子View 的默認(rèn)測(cè)量過程缓屠,其核心邏輯為:-
如果父容器的測(cè)量模式為
EXACTLY
:即父容器測(cè)量大小是確切的,且其剩余空間精確為size
护侮,此時(shí):-
如果 子View 的
LayoutParams
為具體數(shù)值:則表示 子View 已明確設(shè)置了具體大小敌完,因此,此時(shí) 子View 的測(cè)量大小即為自己設(shè)置的值羊初,即childDimension
滨溉,測(cè)量模式為EXACTLY
。 -
如果 子View 的
LayoutParams
為MATCH_PARENT
:表示 子View 的大小撐滿父容器长赞,由于父容器是EXACTLY
晦攒,即大小已知,因此涧卵,子View 也是大小已知勤家,故其測(cè)量模式為EXACTLY
,且其測(cè)量大小就是父容器剩余空間大小柳恐,具體為size
。 -
如果 子View 的
LayoutParams
為WRAP_CONTENT
:表示 子View 自適應(yīng)內(nèi)容大小热幔,但是其尺寸最大不能超過父容器剩余空間乐设,因此其測(cè)量模式為AT_MOST
,測(cè)量大小為父容器剩余空間size
绎巨。
-
如果 子View 的
-
如果父容器的測(cè)量模式為
AT_MOST
:即父容器自適應(yīng)其內(nèi)容大小近尚,也即父容器大小不確定,此時(shí):-
如果 子View 的
LayoutParams
為具體數(shù)值:則表示 子View 已明確設(shè)置了具體大小场勤,因此戈锻,此時(shí) 子View 的測(cè)量大小即為自己設(shè)置的值歼跟,即childDimension
,測(cè)量模式為EXACTLY
格遭。 -
如果 子View 的
LayoutParams
為MATCH_PARENT
:表示 子View 的大小撐滿父容器哈街,由于父容器是AT_MOST
,即大小未知拒迅,因此骚秦,子View 也是大小未知,即其測(cè)量模式為AT_MOST
璧微,且其測(cè)量大小不超過父容器剩余空間大小size
作箍。 -
如果 子View 的
LayoutParams
為WRAP_CONTENT
:表示 子View 自適應(yīng)內(nèi)容大小,但是其尺寸最大不能超過父容器剩余空間前硫,因此其測(cè)量模式為AT_MOST
胞得,測(cè)量大小為父容器剩余空間size
。
-
如果 子View 的
-
-
-
如果父容器的測(cè)量模式為
UNSPECIFIED
:即父容器大小無限制屹电,此時(shí):-
如果 子View 的
LayoutParams
為具體數(shù)值:則表示 子View 已明確設(shè)置了具體大小懒震,因此,此時(shí) 子View 的測(cè)量大小即為自己設(shè)置的值嗤详,即childDimension
个扰,測(cè)量模式為EXACTLY
。 -
如果 子View 的
LayoutParams
為MATCH_PARENT
:表示 子View 的大小撐滿父容器葱色,由于父容器大小無限制递宅,因此,子View 的大小也是無限制的苍狰,所以办龄,子View 的測(cè)量模式為UNSPECIFIED
,測(cè)量大小未知淋昭,通常設(shè)置為0
俐填,表示無限。 -
如果 子View 的
LayoutParams
為WRAP_CONTENT
:表示 子View 自適應(yīng)內(nèi)容大小翔忽,由于父容器大小無限制英融,因此,子View 的測(cè)量大小也是無限制的歇式,所以其模式為UNSPECIFIED
驶悟,測(cè)量大小無限,通常使用0
進(jìn)行表示材失。
-
如果 子View 的
上述的邏輯總結(jié)如下圖所示:(注:圖片來源于互聯(lián)網(wǎng)痕鳍,侵刪)
image注:前面我們一直強(qiáng)調(diào):子View 的
MeasureSpec
是由其LayoutParams
和父容器的MeasureSpec
共同約束構(gòu)造而成,其實(shí)這部分邏輯就是ViewGroup#getChildMeasureSpec(...)
方法負(fù)責(zé)的,可以很清晰看到笼呆,子View 的MeasureSpec
就是在父容器MeasureSpec
約束下熊响,與其自身LayoutParams
共同協(xié)商決定的。 -
綜上诗赌,無論是對(duì)View
的測(cè)量還是ViewGroup
的測(cè)量汗茄,都是由View#measure(int widthMeasureSpec, int heightMeasureSpec)
方法負(fù)責(zé),然后真正執(zhí)行 View 測(cè)量的是 View 的onMeasure(int widthMeasureSpec, int heightMeasureSpec)
方法境肾。
具體來說剔难,View
直接在onMeasure(...)
中測(cè)量并設(shè)置自己的最終測(cè)量寬/高。在默認(rèn)測(cè)量情況下奥喻,View
的測(cè)量寬/高由其父容器的MeasureSpec
和自身的LayoutParams
共同決定偶宫,當(dāng)View
自身的測(cè)量模式為LayoutParams.UNSPECIFIED
時(shí),其測(cè)量寬/高為android:minWidth
/android:minHeight
和其背景寬/高之間的較大值环鲤,其余情況皆為自身MeasureSpec
指定的測(cè)量尺寸纯趋。
而對(duì)于ViewGroup
來說,由于布局特性的豐富性冷离,只能自己手動(dòng)覆寫onMeasure(...)
方法吵冒,實(shí)現(xiàn)自定義測(cè)量過程,但是總的思想都是先測(cè)量 子View 大小西剥,最終才能確定自己的測(cè)量大小痹栖。
layout 流程
當(dāng)確定了 View 的測(cè)量大小后,接下來就可以來確定 View 的布局位置了瞭空,也即將 View 放置到屏幕具體哪個(gè)位置揪阿。
View layout
View 的布局過程由View#layout(...)
負(fù)責(zé),其源碼如下:
// android/view/View.java
/**
* @param l Left position, relative to parent
* @param t Top position, relative to parent
* @param r Right position, relative to parent
* @param b Bottom position, relative to parent
*/
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
...
setFrame(l, t, r, b);
...
onLayout(changed, l, t, r, b);
...
}
View#layout(...)
主要就做了兩件事:
-
setFrame(...)
:首先通過View#setFrame(...)
來確定自己的布局位置咆畏,其源碼如下:// android/view/View.java protected boolean setFrame(int left, int top, int right, int bottom) { ... // Invalidate our old position invalidate(sizeChanged); mLeft = left; mTop = top; mRight = right; mBottom = bottom; }
setFrame(...)
其實(shí)就是更新記錄 View 的四個(gè)頂點(diǎn)位置南捂,這樣 View 在父容器中的坐標(biāo)位置就確定了。 -
onLayout(...)
:setFrame(...)
是用于確定 View 自身的布局位置旧找,而onLayout(...)
主要用于確定 子View 的布局位置:protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
由于 View 不包含子組件溺健,因此其
onLayout
是一個(gè)空實(shí)現(xiàn)。
ViewGroup layout
ViewGroup 的布局流程由ViewGroup#layout(...)
負(fù)責(zé)钮蛛,其源碼如下:
// android/view/ViewGroup.java
@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
...
@Override
public final void layout(int l, int t, int r, int b) {
...
super.layout(l, t, r, b);
...
}
可以看到鞭缭,ViewGroup#layout(...)
最終也是通過View#layout(...)
完成自身的布局過程,一個(gè)注意的點(diǎn)是愿卒,ViewGroup#layout(...)
是一個(gè)final
方法缚去,因此子類無法覆寫該方法,主要是ViewGroup#layout(...)
方法內(nèi)部對(duì)子視圖動(dòng)畫效果進(jìn)行了相關(guān)設(shè)置琼开。
由于ViewGroup#layout(...)
內(nèi)部最終調(diào)用的還是View#layout(...)
,因此枕荞,ViewGroup#onLayout(...)
就會(huì)得到回調(diào)柜候,用于處理 子View 的布局放置搞动,其源碼如下:
// android/view/ViewGroup.java
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
由于不同的ViewGroup
,其布局特性不同渣刷,因此ViewGroup#onLayout(...)
是一個(gè)抽象方法鹦肿,交由ViewGroup
子類依據(jù)自己的布局特性,擺放其 子View 的位置辅柴。
draw 流程
當(dāng) View 的測(cè)量大小箩溃,布局位置都確定后,就可以最終將該 View 繪制到屏幕上了碌嘀。
View 的繪制過程由View#draw(...)
方法負(fù)責(zé)涣旨,其源碼如下:
// android/view/View.java
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
drawBackground(canvas);
// skip step 2 & 5 if possible (common case)
...
// Step 2, save the canvas' layers
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
...
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
...
if (drawTop) {
...
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
...
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
...
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
...
canvas.drawRect(right - length, top, right, bottom, p);
}
...
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
其實(shí)注釋已經(jīng)寫的很清楚了,View#draw(...)
主要做了以下 6 件事:
繪制背景:
drawBackground(...)
如果有必要的話股冗,保存畫布圖層:
Canvas.saveLayer(...)
-
繪制自己:
onDraw(...)
霹陡,其源碼如下:// android/view/View.java protected void onDraw(Canvas canvas) { }
View#onDraw(...)
是一個(gè)空方法,因?yàn)槊總€(gè) View 的繪制都是不同的止状,自定義 View 時(shí)烹棉,通常會(huì)覆寫該方法,手動(dòng)繪制該 View 內(nèi)容怯疤。 -
繪制子View:
dispatchDraw(...)
浆洗,其源碼如下:// android/view/View.java protected void dispatchDraw(Canvas canvas) { }
由于 View 沒有子元素,因此其
dispatchDraw
是一個(gè)空實(shí)現(xiàn)集峦。查看下
ViewGroup#dispatchDraw(...)
伏社,其源碼如下:// android/view/ViewGroup.java @Override protected void dispatchDraw(Canvas canvas) { ... final int childrenCount = mChildrenCount; final View[] children = mChildren; ... for (int i = 0; i < childrenCount; i++) { ... more |= drawChild(canvas, child, drawingTime); ... } ... } protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }
可以看到,其內(nèi)部主要就是遍歷子View少梁,最后通過
child.draw(...)
讓子View自己進(jìn)行繪制洛口。 如果有必要的話,繪制淡化效果并恢復(fù)圖層:
Canvas.drawRect(...)
-
繪制裝飾:
onDrawForeground(...)
凯沪,其源碼如下:// android/view/View.java public void onDrawForeground(Canvas canvas) { onDrawScrollIndicators(canvas); onDrawScrollBars(canvas); final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; ... foreground.draw(canvas); } }
其實(shí)主要就是繪制滾動(dòng)條第焰,前景圖片等視圖相關(guān)的裝飾。
繪制起始流程
我們知道妨马,在Activity啟動(dòng)過程中挺举,會(huì)調(diào)用到ActivityThread.handleResumeActivity(...),該方法就是 View 視圖繪制的起始之處:
// frameworks/base/core/java/android/app/ActivityThread.java
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
...
// 回調(diào) Activity.onResume() 方法
ActivityClientRecord r = performResumeActivity(token, clearHide);
...
// 獲取當(dāng)前 Activity 實(shí)例
final Activity a = r.activity;
...
// 此處的 window 為與 Activity 綁定的 PhoneWindow烘跺,即 Activity.mWindow
r.window = r.activity.getWindow();
// PhoneWindow 綁定的頂層視圖:DecorView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
// 獲取與 Activity 綁定的 WindowManager湘纵,實(shí)際上是 PhoneWindow 的 WindowManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
...
// 添加 DecorView 到 PhoneWindow 上(相當(dāng)于設(shè)置 Activity 根視圖)
wm.addView(decor, l);
...
}
可以看到,ActivityThread.handleResumeActivity(...)主要就是獲取到當(dāng)前Activity綁定的ViewManager滤淳,最后調(diào)用ViewManager.addView(...)方法將DecorView設(shè)置到PhoneWindow上梧喷,也即設(shè)置到當(dāng)前Activity上。ViewManager是一個(gè)接口,WindowManager繼承ViewManager铺敌,而WindowManagerImpl實(shí)現(xiàn)了接口WindowManager汇歹,此處的ViewManager.addView(...)實(shí)際上調(diào)用的是WindowManagerImpl.addView(...),源碼如下所示:
// frameworks/base/core/java/android/view/WindowManagerImpl.java
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
...
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
...
}
WindowManagerImpl.addView(...)內(nèi)部轉(zhuǎn)發(fā)到WindowManagerGlobal.addView(...):
// frameworks/base/core/java/android/view/WindowManagerGlobal.java
public final class WindowManagerGlobal {
...
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
...
// 實(shí)例化一個(gè) ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
...
// 將 ViewRootImpl 與 DecorView 關(guān)聯(lián)到一起
root.setView(view, wparams, panelParentView);
...
}
...
}
在WindowManagerGlobal.addView(...)內(nèi)部偿凭,會(huì)創(chuàng)建一個(gè)ViewRootImpl實(shí)例产弹,然后調(diào)用ViewRootImpl.setView(...)將ViewRootImpl與DecorView關(guān)聯(lián)到一起:
// frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
...
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
// 將 DecorView 綁定到 ViewRootImpl.mView 屬性上
mView = view;
...
mWindowAttributes.copyFrom(attrs);
...
// 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();
...
scheduleTraversals();
}
}
...
}
ViewRootImpl.setView(...)內(nèi)部首先關(guān)聯(lián)了傳遞過來的DecorView(通過屬性mView指向DecorView即可建立關(guān)聯(lián)),然后最終調(diào)用requestLayout()弯囊,而requestLayout()內(nèi)部又會(huì)調(diào)用方法scheduleTraversals():
// frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
...
Choreographer mChoreographer;
...
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 開始執(zhí)行繪制
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
...
void scheduleTraversals() {
if (!mTraversalScheduled) { // 同一幀內(nèi)不會(huì)多次調(diào)用遍歷
mTraversalScheduled = true;
// 發(fā)送一個(gè)同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 將 UI 繪制任務(wù)發(fā)送到 Choreographer痰哨,回調(diào)觸發(fā) mTraversalRunnable,執(zhí)行繪制操作
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
...
void doTraversal() {
...
performTraversals();
...
}
...
}
ViewRootImpl.scheduleTraversals()內(nèi)部主要做了兩件事:
調(diào)用MessageQueue.postSyncBarrier()方法發(fā)送一個(gè)同步屏障匾嘱,同步屏障可以攔截Looper對(duì)同步消息的獲取與分發(fā),即加入同步屏障后奄毡,此時(shí)Looper只會(huì)獲取和處理異步消息折欠,如果沒有異步消息,則進(jìn)入阻塞狀態(tài)吼过。
通過Choreographer.postCallback(...)發(fā)送一個(gè)Choreographer.CALLBACK_TRAVERSAL的異步視圖渲染消息锐秦。因?yàn)榍懊嬉呀?jīng)發(fā)送了一個(gè)同步屏障,因此此處的視圖繪制渲染消息會(huì)優(yōu)先被處理盗忱。
Choreographer.postCallback(...)會(huì)申請(qǐng)一次 VSYNC 中斷信號(hào)酱床,當(dāng) VSYNC 信號(hào)到達(dá)時(shí),便會(huì)回調(diào)Choreographer.doFrame(...)方法趟佃,內(nèi)部會(huì)觸發(fā)已經(jīng)添加的回調(diào)任務(wù)扇谣,Choreographer的回調(diào)任務(wù)有以下四種類型:
// 回調(diào) INPUT 任務(wù)
doCallbacks(Choreographer.CALLBACK_INPUT, mframeTimeNanos);
// 回調(diào) ANIMATION
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
// 回調(diào) View 繪制任務(wù) TRAVERSAL
doCallbacks(Choreographer,CALLBACK_TRAVERSAL, frameTimeNanos);
// API Level 23 新增闲昭,COMMIT
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
因此罐寨,ViewRootImpl.scheduleTraversals(...)內(nèi)部通過
<meta charset="utf-8">
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)
發(fā)送的異步視圖渲染消息就會(huì)得到回調(diào),即回調(diào)mTraversalRunnable.run()
方法序矩,最終會(huì)執(zhí)行doTraversal()
方法鸯绿,而doTraversal()
內(nèi)部又會(huì)調(diào)用performTraversals()
方法,該方法才是真正開始執(zhí)行 View 繪制流程的地方簸淀,其源碼如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
...
private void performTraversals() {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
performDraw();
...
}
...
}
綜上瓶蝴,performTraversals()
會(huì)依次調(diào)用performMeasure(...)
、performLayout(...)
和performDraw()
三個(gè)方法租幕,這三個(gè)方法會(huì)依次完成頂層View(即DecorView
)的測(cè)量(measure
)舷手、布局(layout
)和繪制(draw
)流程,具體詳情請(qǐng)參考后文劲绪。
到此男窟,我們才真正進(jìn)入 View 繪制流程盆赤,總結(jié)一下上述流程,如下圖所示:
performMeasure
書接前文蝎宇,我們知道弟劲,真正開始 View 繪制流程是ViewRootImpl.performTraversals()
祷安,該方法內(nèi)部首先進(jìn)行的是performMeasure(...)
流程:
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
// 調(diào)用 DecorView.measure(...)
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
此處的mView
其實(shí)就是DecorView
姥芥,其賦值指向在ViewRootImpl.setView(...)
中進(jìn)行,可以看到汇鞭,performMeasure(...)
實(shí)際調(diào)用的是DecorView.measure(...)
凉唐,所以最終會(huì)回調(diào)DecorView#onMeasure(...)
方法,其源碼如下:
// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
...
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
...
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
...
}
可以看到霍骄,DecorView#onMeasure(...)
內(nèi)部將測(cè)量過程交由其父類台囱,即FrameLayout
進(jìn)行處理,那我們看下FrameLayout#onMeasure(...)
源碼:
// frameworks/base/core/java/android/widget/FrameLayout.java
public class FrameLayout extends ViewGroup {
...
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 獲取 子View 數(shù)量
int count = getChildCount();
...
// 最大高度
int maxHeight = 0;
// 最大寬度
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
// 獲取 子View
final View child = getChildAt(i);
// 只對(duì)可見的 子View 進(jìn)行測(cè)量
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 測(cè)量子View
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
// 獲取 子View 的布局參數(shù)
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 獲取當(dāng)前子View的寬度读整,包含其外邊距簿训,記錄子View的最大寬度
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
// 記錄子View的最大高度
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
...
}
}
// Account for padding too
// 最大寬度包含前景偏移量:padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
// 最大高度包含前景偏移量:padding
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
// 比較子View 和 系統(tǒng)建議的 子View 最小高度,獲取兩者中的較大值
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
// 比較子View 和 系統(tǒng)建議的 子View 最小寬度米间,獲取兩者中的較大值
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
// 子View 高度和 前景圖片高度比較强品,記錄其中較大值
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
// 子View 高度和 前景圖片寬度比較,記錄其中較大值
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
// 記錄測(cè)量結(jié)果
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
...
}
...
}
FrameLayout
的布局特性為:所有 子View 層疊在一起屈糊,所以FrameLayout
的測(cè)量寬/高就是其所有 子View 中最大的寬和高的榛,因此FrameLayout#onMeasure(...)
的核心邏輯就是遍歷其所有子View,然后通過measureChildWithMargins(...)
(該方法前面內(nèi)容已詳細(xì)介紹)測(cè)量子View逻锐,然后就可以獲取 子View 的寬/高夫晌,記錄其中最大的寬/高值,作為自己的測(cè)量寬/高昧诱。
經(jīng)過以上步驟晓淀,DecorView
的測(cè)量就已經(jīng)完成了。
綜上盏档,ViewRootImpl#performMeasure(...)
其實(shí)就是對(duì)DecorView
的測(cè)量過程(DecorView#measure(...)
)凶掰,DecorView
是一個(gè)FrameLayout
,其測(cè)量過程主要由FrameLayout#onMeasure(...)
負(fù)責(zé)妆丘,內(nèi)部主要測(cè)量邏輯是先遍歷所有子View锄俄,讓 子View 先自己進(jìn)行測(cè)量(child.measure(...)
)勺拣,然后就可以獲取 子View 的測(cè)量大小毅戈,記錄所有 子View 中占比最大的測(cè)量寬/高,作為自己的最終測(cè)量大小。
<meta charset="utf-8">
performLayout
ViewRootImpl#performMeasure(...)
完成對(duì)DecorView
的測(cè)量后,接下來執(zhí)行的是ViewRootImpl#performLayout(...)
,其源碼如下:
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
// cache mView since it is used so much below...
final View host = mView;
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
其中,參數(shù)lp
的width
和height
均為MATCH_PARENT
宾巍,desiredWindowWidth
和desiredWindowHeight
為屏幕寬/高蜀漆,mView
為DecorView
。
所以鲜侥,performLayout(...)
內(nèi)部其實(shí)就是調(diào)用DecorView#layout(...)
狐粱,前面 layout 流程中介紹過,ViewGroup#layout(...)
內(nèi)部最終會(huì)通過View#layout(...)
進(jìn)行布局判莉,而View#layout(...)
內(nèi)部最終通過View#setFrame(...)
方法記錄四個(gè)頂點(diǎn)位置膛檀,這樣DecorView
自己的布局位置就已確定了,即host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight())
互站。
確定了DecorView
自身的布局位置后僵缺,接下來就是要布局其 子View 了踩叭,因此磕潮,這里最終回調(diào)的是DecorView#onLayout(...)
方法容贝,其源碼如下所示:
// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
...
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
...
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
...
}
...
}
DecorView#onLayout(...)
內(nèi)部轉(zhuǎn)交給FrameLayout#onLayout(...)
進(jìn)行 子View 布局操作焕参,其源碼如下:
// frameworks/base/core/java/android/widget/FrameLayout.java
public class FrameLayout extends ViewGroup {
...
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// 布局子View
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom,
boolean forceLeftGravity) {
// 獲取 子View 數(shù)量
final int count = getChildCount();
// 左邊可放置起始點(diǎn)坐標(biāo)
final int parentLeft = getPaddingLeftWithForeground();
// 右邊可放置終點(diǎn)坐標(biāo)
final int parentRight = right - left - getPaddingRightWithForeground();
// 頂部可放置起始點(diǎn)坐標(biāo)
final int parentTop = getPaddingTopWithForeground();
// 底部可放置終點(diǎn)坐標(biāo)
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
// 遍歷 子View
for (int i = 0; i < count; i++) {
// 獲取 子View
final View child = getChildAt(i);
// 不放置狀態(tài)為 GONE 的子View
if (child.getVisibility() != GONE) {
// 獲取 子View 布局參數(shù)
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 獲取 子View 測(cè)量寬/高
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
// 當(dāng)前 子View 的布局左邊界
int childLeft;
// 當(dāng)前 子View 的布局右邊界
int childTop;
...
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
...
}
FrameLayout#onLayout(...)
內(nèi)部是通過FrameLayout#layoutChildren(...)
進(jìn)行 子View 的布局操作,其主要邏輯就是遍歷所有 子View潦嘶,計(jì)算得到 子View 的四個(gè)頂點(diǎn)位置坐標(biāo)涩嚣,最后將結(jié)果傳遞給child.layout(...)
,讓 子View 記錄自己在父容器中的布局位置掂僵,完成 子View 的布局過程航厚。
綜上,ViewRootImpl#performLayout(...)
就是對(duì)DecorView
的布局過程锰蓬,此過程會(huì)遞歸計(jì)算各個(gè) 子View 的布局位置幔睬,調(diào)用 子View 的布局方法,完成各個(gè) 子View 的布局互妓。
performDraw
完成了performMeasure(...)
和performLayout(...)
后溪窒,最后一步就是performDraw(...)
過程坤塞,其源碼如下:
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performDraw() {
...
draw(fullRedrawNeeded);
...
}
private void draw(boolean fullRedrawNeeded) {
...
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
...
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
...
mView.draw(canvas);
...
}
可以看到,ViewRootImpl#performDraw()
內(nèi)部會(huì)經(jīng)由ViewRootImpl#draw(...)
澈蚌、ViewRootImpl#drawSoftware(...)
摹芙,最終執(zhí)行的還是DecorView#draw(...)
過程,其源碼如下:
// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
...
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mMenuBackground != null) {
mMenuBackground.draw(canvas);
}
}
...
}
由于FrameLayout
沒有覆寫draw(...)
方法宛瞄,因此浮禾,super.draw(...)
最終調(diào)用的是View#draw(...)
方法,所以DecorView
默認(rèn)采用的就是 View 的繪制方法份汗,具體繪制詳情上文已介紹過了盈电,主要就是對(duì)DecorView
的背景、內(nèi)容杯活、子View匆帚、滾動(dòng)條等裝飾視圖進(jìn)行繪制。
至此旁钧,View 繪制的整個(gè)流程已基本介紹完畢吸重。
總結(jié)
View 的繪制主要有以下一些核心內(nèi)容:
-
三大流程:View 繪制主要包含如下三大流程:
-
measure:測(cè)量流程,主要負(fù)責(zé)對(duì) View 進(jìn)行測(cè)量歪今,其核心邏輯位于
View#measure(...)
嚎幸,真正的測(cè)量處理由View#onMeasure(...)
負(fù)責(zé)。默認(rèn)的測(cè)量規(guī)則為:如果 View 的布局參數(shù)為LayoutParams.WRAP_CONTENT
或LayoutParams.MATCH_PARENT
寄猩,那么其測(cè)量大小為 SpecSize嫉晶;如果其布局參數(shù)為LayoutParams.UNSPECIFIED
,那么其測(cè)量大小為android:minWidth
/android:minHeight
和其背景之間的較大值田篇。
自定義View 通常覆寫
onMeasure(...)
方法替废,在其內(nèi)一般會(huì)對(duì)WRAP_CONTENT
預(yù)設(shè)一個(gè)默認(rèn)值,區(qū)分WARP_CONTENT
和MATCH_PARENT
效果斯辰,最終完成自己的測(cè)量寬/高舶担。而ViewGroup
在onMeasure(...)
方法中,通常都是先測(cè)量子View彬呻,收集到相應(yīng)數(shù)據(jù)后衣陶,才能最終測(cè)量自己。-
layout:布局流程闸氮,主要完成對(duì) View 的位置放置剪况,其核心邏輯位于
View#layout(...)
,該方法內(nèi)部主要通過View#setFrame(...)
記錄自己的四個(gè)頂點(diǎn)坐標(biāo)(記錄與對(duì)應(yīng)成員變量中即可)蒲跨,完成自己的位置放置译断,最后會(huì)回調(diào)View#onLayout(...)
方法,在其內(nèi)完成對(duì) 子View 的布局放置或悲。注:不同于 measure 流程首先對(duì) 子View 進(jìn)行測(cè)量孙咪,最后才測(cè)量自己堪唐,layout 流程首先是先定位自己的布局位置,然后才處理放置 子View 的布局位置翎蹈。
-
draw:繪制流程淮菠,就是將 View 繪制到屏幕上,其核心邏輯位于
View#draw(...)
荤堪,主要就是對(duì) 背景合陵、自身內(nèi)容(onDraw(...)
)、子View(dispatchDraw(...)
)澄阳、裝飾(滾動(dòng)條拥知、前景等) 進(jìn)行繪制。注:通常自定義View 覆寫
onDraw(...)
方法碎赢,完成自己的繪制即可低剔,ViewGroup 一般充當(dāng)容器使用,因此通常無需覆寫onDraw(...)
揩抡。
-
measure:測(cè)量流程,主要負(fù)責(zé)對(duì) View 進(jìn)行測(cè)量歪今,其核心邏輯位于
Activity 的根視圖(即
DecorView
)最終是綁定到ViewRootImpl
户侥,具體是由ViewRootImpl#setView(...)
進(jìn)行綁定關(guān)聯(lián)的,后續(xù) View 繪制的三大流程都是均有ViewRootImpl
負(fù)責(zé)執(zhí)行的峦嗤。對(duì) View 的測(cè)量流程中,最關(guān)鍵的一步是求取 View 的
MeasureSpec
屋摔,View 的MeasureSpec
是在其父容器MeasureSpec
的約束下烁设,結(jié)合自己的LayoutParams
共同測(cè)量得到的,具體的測(cè)量邏輯由ViewGroup#getChildMeasureSpec(...)
負(fù)責(zé)钓试。
DecorView
的MeasureSpec
取決于自己的LayoutParams
和屏幕尺寸装黑,具體的測(cè)量邏輯位于ViewRootImpl#getRootMeasureSpec(...)
。
最后弓熏,稍微總結(jié)一下 View 繪制的整個(gè)流程:
-
首先恋谭,當(dāng) Activity 啟動(dòng)時(shí),會(huì)觸發(fā)調(diào)用到
ActivityThread#handleResumeActivity(..)
挽鞠,其內(nèi)部會(huì)經(jīng)歷一系列過程疚颊,生成DecorView
和ViewRootImpl
等實(shí)例,最后通過ViewRootImpl#setView(decor,MATCH_PARENT)
設(shè)置 Activity 根View信认。注:
ViewRootImpl#setView(...)
內(nèi)容通過將其成員屬性ViewRootImpl#mView
指向DecorView
材义,完成兩者之間的關(guān)聯(lián)。 ViewRootImpl
成功關(guān)聯(lián)DecorView
后嫁赏,其內(nèi)部會(huì)設(shè)置同步屏障并發(fā)送一個(gè)CALLBACK_TRAVERSAL
異步渲染消息其掂,在下一次 VSYNC 信號(hào)到來時(shí),CALLBACK_TRAVERSAL
就會(huì)得到響應(yīng)潦蝇,從而最終觸發(fā)執(zhí)行ViewRootImpl#performTraversals(...)
款熬,真正開始執(zhí)行 View 繪制流程深寥。-
ViewRootImpl#performTraversals(...)
內(nèi)部會(huì)依次調(diào)用ViewRootImpl#performMeasure(...)
、ViewRootImpl#performLayout(...)
和ViewRootImpl#performDraw(...)
三大繪制流程贤牛,其中:performMeasure(..)
:內(nèi)部主要就是對(duì)DecorView
執(zhí)行測(cè)量流程:DecorView#measure(...)
惋鹅。DecorView
是一個(gè)FrameLayout
,其布局特性是層疊布局盔夜,所占的空間就是其 子View 占比最大的寬/高负饲,因此其測(cè)量邏輯(onMeasure(...)
)是先對(duì)所有 子View 進(jìn)行測(cè)量,具體是通過ViewGroup#measureChildWithMargins(...)
方法對(duì) 子View 進(jìn)行測(cè)量喂链,子View 測(cè)量完成后返十,記錄最大的寬/高,設(shè)置為自己的測(cè)量大型治ⅰ(通過View#setMeasuredDimension(...)
)洞坑,如此便完成了DecorView
的測(cè)量流程。performLayout(...)
:內(nèi)部其實(shí)就是調(diào)用DecorView#layout(...)
蝇率,如此便完成了DecorView
的布局位置迟杂,最后會(huì)回調(diào)DecorView#onLayout(...)
,負(fù)責(zé) 子View 的布局放置本慕,核心邏輯就是計(jì)算出各個(gè) 子View 的坐標(biāo)位置排拷,最后通過child.layout(...)
完成 子View 布局。performDraw()
:內(nèi)部最終調(diào)用到的是DecorView#draw(...)
锅尘,該方法內(nèi)部并未對(duì)繪制流程做任何修改本缠,因此最終執(zhí)行的是View#draw(...)
者疤,所以主要就是依次完成對(duì)DecorView
的 背景、子View(dispatchDraw(...)
) 和 視圖裝飾(滾動(dòng)條、前景等) 的繪制阶女。
作者:Whyn
鏈接:http://www.reibang.com/p/ee5d3bb5ab90
來源:簡(jiǎn)書
著作權(quán)歸作者所有蛔屹。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)刷允,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處割捅。