
簡(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 過(guò)程,而ViewGroup通常不需要(當(dāng)然也可以進(jìn)行繪制),因?yàn)?code>ViewGroup更多作為容器存在,起存儲(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,首先我們來(lái)看下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è)量大?。?br>
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 的最大尺寸不能超過(guò)父容器的 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)容大?。ㄗⅲ盒枰覸iew 本身的padding空間) -
width/height:表示 View 的設(shè)置寬/高,即layout_width和layout_height設(shè)置的值,其值有三種選擇:LayoutParams.MATCH_PARENT、LayoutParams.WRAP_CONTENT和 具體數(shù)值。
LayoutParams會(huì)受到父容器的MeasureSpec的影響,測(cè)量過(guò)程會(huì)依據(jù)兩者之間的相互約束最終生成子View 的MeasureSpec,完成 View 的測(cè)量規(guī)格。
簡(jiǎn)而言之,View 的MeasureSpec受自身的LayoutParams和父容器的MeasureSpec共同決定(DecorView的MeasureSpec是由自身的LayoutParams和屏幕尺寸共同決定,參考后文)。也因此,如果要求取子View 的MeasureSpec,那么首先就需要知道父容器的MeasureSpec,層層逆推而上,即最終就是需要知道頂層View(即DecorView)的MeasureSpec,這樣才能一層層傳遞下來(lái),這整個(gè)過(guò)程需要結(jié)合Activity的啟動(dòng)過(guò)程進(jìn)行分析。
Activity 視圖基本結(jié)構(gòu)
我們知道,在 Android 中,Activity是作為視圖組件存在,主要就是在手機(jī)上顯示視圖界面,可以供用戶操作,Activity就是 Andorid 中與用戶直接交互最多的系統(tǒng)組件。
Activity的基本視圖層次結(jié)構(gòu)如下所示:
Activity中,實(shí)際承載視圖的組件是Window(更具體來(lái)說(shuō)為PhoneWindow),頂層View 是DecorView,它是一個(gè)FrameLayout,DecorView內(nèi)部是一個(gè)LinearLayout,該LinearLayout由兩部分組成(不同 Android 版本或主題稍有差異):TitleView和ContentView,其中,TitleView就是標(biāo)題欄,也就是我們常說(shuō)的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。大致過(guò)程如下所示:
-
在
Activity啟動(dòng)過(guò)程中,會(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,最后通過(guò)wm.addView(decor,l)將DecorView添加到WindowManager上(最終其實(shí)是設(shè)置到ViewRootImpl上),所以DecorView的布局參數(shù)為MATCH_PARENT。 -
View 的繪制流程真正開(kāi)始的地方為
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(...)見(jiàn)名知意,其實(shí)就是用來(lái)獲取頂層View(即DecorView)的MeasureSpec,其邏輯如下:- 當(dāng)
DecorView的LayoutParams為MATCH_PARENT時(shí),說(shuō)明DecorView的大小與屏幕一樣大,而又由于屏幕大小是確定的,因此,其 SpecMode 為EXACTLY,SpecSize 為windowSize,; - 當(dāng)
DecorView的LayoutParams為WRAP_CONTENT時(shí),說(shuō)明DecorView自適應(yīng)內(nèi)容大小,因此它的大小不確定,但是最大不能超過(guò)屏幕大小,故其 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)過(guò)上述步驟求取得到 View 的MeasureSpec后,接下來(lái)就可以真正對(duì) View 進(jìn)行測(cè)量,求取 View 的最終測(cè)量寬/高:
Android 內(nèi)部對(duì)視圖進(jìn)行測(cè)量的過(guò)程是由View#measure(int, int)方法負(fù)責(zé)的,但是對(duì)于View和ViewGroup,其具體測(cè)量過(guò)程有所差異。
因此,對(duì)于測(cè)量過(guò)程,我們分別對(duì)View和ViewGroup進(jìn)行分析:
-
View測(cè)量:View的測(cè)量過(guò)程由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)來(lái)的,具體的測(cè)量過(guò)程請(qǐng)參考后文內(nèi)容。需要注意的是,
View#measure(int, int)是一個(gè)final方法,因此其不可被覆寫(xiě),實(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(...)主要做了三件事:-
首先通過(guò)
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 沒(méi)有設(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 沒(méi)有設(shè)置背景時(shí),其最小寬/高為android:minWidth/android:mMinHeight所指定的值,當(dāng) View 設(shè)置了背景時(shí),其最小測(cè)量寬/高為android:minWidth/android:minHeight與其背景圖片寬/高的較大值。簡(jiǎn)而言之,View 的最小測(cè)量寬/高為
android:minWidth/android:minHeight和其背景寬/高之間的較大值。 -
通過(guò)
getDefaultSize(...)獲取到 View 的默認(rèn)測(cè)量寬/高,具體獲取過(guò)程如下所示:// 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是通過(guò)getSuggestedMinimumWidth()/getSuggestedMinimumHeight()方法獲取得到系統(tǒng)建議 View 的最小測(cè)量寬/高。參數(shù)
measureSpec是經(jīng)由View.measure(...)->View.onMeasure(...)->View.getDefaultSize(...)調(diào)用鏈傳遞進(jìn)來(lái)的,表示的是當(dāng)前 View 的MeasureSpec。getDefaultSize(...)內(nèi)部首先會(huì)獲取 View 的測(cè)量模式和測(cè)量大小,然后當(dāng) View 的測(cè)量模式為UNSPECIFIED時(shí),也即未限制 View 的大小,因此此時(shí) View 的大小就是其原生大?。ㄒ布?code>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)來(lái)的,父容器是通過(guò)ViewGroup#getChildMeasureSpec(...)方法獲取得到 子View 的MeasureSpec,在該方法內(nèi)部,子View 的測(cè)量模式無(wú)論是AT_MOST或是EXACTLY,其測(cè)量大小都為父容器大小(確定的說(shuō),是父容器剩余空間大?。虼似湫Ч偷韧?code>MATCH_PARENT,具體源碼詳情分析請(qǐng)參考后文。總之,一般自定義 View 時(shí),都需要覆寫(xiě)
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è)量寬/高后,通過(guò)
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è)量過(guò)程。
-
-
ViewGroup測(cè)量:ViewGroup是一個(gè)抽象類,其繼承于View:public abstract class ViewGroup extends View implements ViewParent, ViewManager {...}ViewGroup的測(cè)量過(guò)程也是由View.measure(...)負(fù)責(zé),因此實(shí)際負(fù)責(zé)測(cè)量的是ViewGroup.onMeasure(...)方法,但是由于ViewGroup的作用是用于容納子View,如果想測(cè)量ViewGroup,則必須先測(cè)量其子View,而又由于不同的ViewGroup有不同的布局特性,因此無(wú)法抽象出一套標(biāo)準(zhǔn)的測(cè)量流程,所以ViewGroup本身沒(méi)有覆寫(xiě)onMeasure(...)方法(交由具體自定義ViewGroup覆寫(xiě)),但是它提供了一些測(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)介紹過(guò)了,這里我們主要來(lái)看下ViewGroup#getChildMeasureSpec(...)獲取 子View 測(cè)量規(guī)格的具體過(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 // 父容器大小無(wú)限制 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è)量過(guò)程,其核心邏輯為:-
如果父容器的測(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)容大小,但是其尺寸最大不能超過(guò)父容器剩余空間,因此其測(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è)量大小不超過(guò)父容器剩余空間大小size。 -
如果 子View 的
LayoutParams為WRAP_CONTENT:表示 子View 自適應(yīng)內(nèi)容大小,但是其尺寸最大不能超過(guò)父容器剩余空間,因此其測(cè)量模式為AT_MOST,測(cè)量大小為父容器剩余空間size。
-
如果 子View 的
-
如果父容器的測(cè)量模式為
UNSPECIFIED:即父容器大小無(wú)限制,此時(shí):-
如果 子View 的
LayoutParams為具體數(shù)值:則表示 子View 已明確設(shè)置了具體大小,因此,此時(shí) 子View 的測(cè)量大小即為自己設(shè)置的值,即childDimension,測(cè)量模式為EXACTLY。 -
如果 子View 的
LayoutParams為MATCH_PARENT:表示 子View 的大小撐滿父容器,由于父容器大小無(wú)限制,因此,子View 的大小也是無(wú)限制的,所以,子View 的測(cè)量模式為UNSPECIFIED,測(cè)量大小未知,通常設(shè)置為0,表示無(wú)限。 -
如果 子View 的
LayoutParams為WRAP_CONTENT:表示 子View 自適應(yīng)內(nèi)容大小,由于父容器大小無(wú)限制,因此,子View 的測(cè)量大小也是無(wú)限制的,所以其模式為UNSPECIFIED,測(cè)量大小無(wú)限,通常使用0進(jìn)行表示。
-
如果 子View 的
上述的邏輯總結(jié)如下圖所示:(注:圖片來(lái)源于互聯(lián)網(wǎng),侵刪)
ViewGroup#getChildMeasureSpec注:前面我們一直強(qiáng)調(diào):子View 的
MeasureSpec是由其LayoutParams和父容器的MeasureSpec共同約束構(gòu)造而成,其實(shí)這部分邏輯就是ViewGroup#getChildMeasureSpec(...)方法負(fù)責(zé)的,可以很清晰看到,子View 的MeasureSpec就是在父容器MeasureSpec約束下,與其自身LayoutParams共同協(xié)商決定的。 -
綜上,無(wú)論是對(duì)View的測(cè)量還是ViewGroup的測(cè)量,都是由View#measure(int widthMeasureSpec, int heightMeasureSpec)方法負(fù)責(zé),然后真正執(zhí)行 View 測(cè)量的是 View 的onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法。
具體來(lái)說(shuō),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來(lái)說(shuō),由于布局特性的豐富性,只能自己手動(dòng)覆寫(xiě)onMeasure(...)方法,實(shí)現(xiàn)自定義測(cè)量過(guò)程,但是總的思想都是先測(cè)量 子View 大小,最終才能確定自己的測(cè)量大小。
layout 流程
當(dāng)確定了 View 的測(cè)量大小后,接下來(lái)就可以來(lái)確定 View 的布局位置了,也即將 View 放置到屏幕具體哪個(gè)位置。
View layout
View 的布局過(guò)程由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(...):首先通過(guò)View#setFrame(...)來(lái)確定自己的布局位置,其源碼如下:// 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(...)最終也是通過(guò)View#layout(...)完成自身的布局過(guò)程,一個(gè)注意的點(diǎn)是,ViewGroup#layout(...)是一個(gè)final方法,因此子類無(wú)法覆寫(xiě)該方法,主要是ViewGroup#layout(...)方法內(nèi)部對(duì)子視圖動(dòng)畫(huà)效果進(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 的繪制過(guò)程由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)寫(xiě)的很清楚了,View#draw(...)主要做了以下 6 件事:
繪制背景:
drawBackground(...)如果有必要的話,保存畫(huà)布圖層:
Canvas.saveLayer(...)-
繪制自己:
onDraw(...),其源碼如下:// android/view/View.java protected void onDraw(Canvas canvas) { }View#onDraw(...)是一個(gè)空方法,因?yàn)槊總€(gè) View 的繪制都是不同的,自定義 View 時(shí),通常會(huì)覆寫(xiě)該方法,手動(dòng)繪制該 View 內(nèi)容。 -
繪制子View:
dispatchDraw(...),其源碼如下:// android/view/View.java protected void dispatchDraw(Canvas canvas) { }由于 View 沒(méi)有子元素,因此其
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,最后通過(guò)
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)過(guò)程中,會(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)了傳遞過(guò)來(lái)的DecorView(通過(guò)屬性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() {
// 開(kāi)始執(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ì)獲取和處理異步消息,如果沒(méi)有異步消息,則進(jìn)入阻塞狀態(tài)。 - 通過(guò)
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)部通過(guò)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()方法,該方法才是真正開(kāi)始執(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
書(shū)接前文,我們知道,真正開(kāi)始 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è)量過(guò)程交由其父類,即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ì)可見(jiàn)的 子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,然后通過(guò)measureChildWithMargins(...)(該方法前面內(nèi)容已詳細(xì)介紹)測(cè)量子View,然后就可以獲取 子View 的寬/高,記錄其中最大的寬/高值,作為自己的測(cè)量寬/高。
經(jīng)過(guò)以上步驟,DecorView的測(cè)量就已經(jīng)完成了。
綜上,ViewRootImpl#performMeasure(...)其實(shí)就是對(duì)DecorView的測(cè)量過(guò)程(DecorView#measure(...)),DecorView是一個(gè)FrameLayout,其測(cè)量過(guò)程主要由FrameLayout#onMeasure(...)負(fù)責(zé),內(nèi)部主要測(cè)量邏輯是先遍歷所有子View,讓 子View 先自己進(jìn)行測(cè)量(child.measure(...)),然后就可以獲取 子View 的測(cè)量大小,記錄所有 子View 中占比最大的測(cè)量寬/高,作為自己的最終測(cè)量大小。
performLayout
ViewRootImpl#performMeasure(...)完成對(duì)DecorView的測(cè)量后,接下來(lái)執(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 流程中介紹過(guò),ViewGroup#layout(...)內(nèi)部最終會(huì)通過(guò)View#layout(...)進(jìn)行布局,而View#layout(...)內(nèi)部最終通過(guò)View#setFrame(...)方法記錄四個(gè)頂點(diǎn)位置,這樣DecorView自己的布局位置就已確定了,即host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight())。
確定了DecorView自身的布局位置后,接下來(lái)就是要布局其 子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)部是通過(guò)FrameLayout#layoutChildren(...)進(jìn)行 子View 的布局操作,其主要邏輯就是遍歷所有 子View,計(jì)算得到 子View 的四個(gè)頂點(diǎn)位置坐標(biāo),最后將結(jié)果傳遞給child.layout(...),讓 子View 記錄自己在父容器中的布局位置,完成 子View 的布局過(guò)程。
綜上,ViewRootImpl#performLayout(...)就是對(duì)DecorView的布局過(guò)程,此過(guò)程會(huì)遞歸計(jì)算各個(gè) 子View 的布局位置,調(diào)用 子View 的布局方法,完成各個(gè) 子View 的布局。
performDraw
完成了performMeasure(...)和performLayout(...)后,最后一步就是performDraw(...)過(guò)程,其源碼如下:
// 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(...)過(guò)程,其源碼如下:
// 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沒(méi)有覆寫(xiě)draw(...)方法,因此,super.draw(...)最終調(diào)用的是View#draw(...)方法,所以DecorView默認(rèn)采用的就是 View 的繪制方法,具體繪制詳情上文已介紹過(guò)了,主要就是對(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 通常覆寫(xiě)
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)部主要通過(guò)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 覆寫(xiě)
onDraw(...)方法,完成自己的繪制即可,ViewGroup 一般充當(dāng)容器使用,因此通常無(wú)需覆寫(xiě)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)歷一系列過(guò)程,生成DecorView和ViewRootImpl等實(shí)例,最后通過(guò)ViewRootImpl#setView(decor,MATCH_PARENT)設(shè)置 Activity 根View。注:
ViewRootImpl#setView(...)內(nèi)容通過(guò)將其成員屬性ViewRootImpl#mView指向DecorView,完成兩者之間的關(guān)聯(lián)。 ViewRootImpl成功關(guān)聯(lián)DecorView后,其內(nèi)部會(huì)設(shè)置同步屏障并發(fā)送一個(gè)CALLBACK_TRAVERSAL異步渲染消息,在下一次 VSYNC 信號(hào)到來(lái)時(shí),CALLBACK_TRAVERSAL就會(huì)得到響應(yīng),從而最終觸發(fā)執(zhí)行ViewRootImpl#performTraversals(...),真正開(kāi)始執(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è)量,具體是通過(guò)ViewGroup#measureChildWithMargins(...)方法對(duì) 子View 進(jìn)行測(cè)量,子View 測(cè)量完成后,記錄最大的寬/高,設(shè)置為自己的測(cè)量大?。ㄍㄟ^(guò)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)位置,最后通過(guò)child.layout(...)完成 子View 布局。performDraw():內(nèi)部最終調(diào)用到的是DecorView#draw(...),該方法內(nèi)部并未對(duì)繪制流程做任何修改,因此最終執(zhí)行的是View#draw(...),所以主要就是依次完成對(duì)DecorView的 背景、子View(dispatchDraw(...)) 和 視圖裝飾(滾動(dòng)條、前景等) 的繪制。
