Android View的繪制
1. 概述
我們在Android的開發(fā)工作中都在不停地跟View打交道,Android中的任何一個(gè)布局、控件其實(shí)都是直接或間接繼承自View的,如TextView、Button、ImageView、ListView等。這些控件雖然是Android系統(tǒng)本身就提供好的,我們只需要拿過來使用就可以了,但多知道一些總是沒有壞處的,接下來就將介紹View是如何被繪制到屏幕上的。
任何一個(gè)視圖都不可能憑空突然出現(xiàn)在屏幕上,它們都是要經(jīng)過非常科學(xué)的繪制流程后才能顯示出來的。每一個(gè)視圖的繪制過程都必須經(jīng)歷三個(gè)最主要的階段,即onMeasure()、onLayout()和onDraw(),這也是我們最主要的介紹部分,但在開始之前我們應(yīng)當(dāng)先了解一下Android的窗口結(jié)構(gòu)。
2. Android的窗口結(jié)構(gòu)
簡介
先來看一張圖:

Activity
- Activity并不負(fù)責(zé)視圖控制(例如添加或者刪除view),它只是控制生命周期和處理事件。
- 每一個(gè)Activity都包含一個(gè)根Window對象,Window才是真正代表一個(gè)窗口,Window對象通常由PhoneWindow實(shí)現(xiàn)
- Activity更像一個(gè)控制器,統(tǒng)籌視圖的添加與顯示,以及通過其他回調(diào)方法,來與Window、以及View進(jìn)行交互。
Window(PhoneWindow)
Window的唯一實(shí)現(xiàn)類為PhoneWindow,而DecorView為PhoneWindow的內(nèi)部類,PhoneWindow持有該內(nèi)部類對象mDecor,所以真正持有和控制視圖的是PhoneWindow
DecorView
- DecorView是FrameLayout的子類
- 是當(dāng)前activity中所有view的祖先,我們在activity中調(diào)用setVisible(boolean visiable)時(shí),實(shí)際上就是設(shè)置DecorView的可見性
- DecorView是FrameLayout的子類,它可以被認(rèn)為是Android視圖樹的根節(jié)點(diǎn)視圖。DecorView作為頂級View,一般情況下它內(nèi)部包含一個(gè)豎直方向的LinearLayout,在這個(gè)LinearLayout里面有上下三個(gè)部分,上面是個(gè)ViewStub,延遲加載的視圖(應(yīng)該是設(shè)置ActionBar,根據(jù)Theme設(shè)置),中間的是標(biāo)題欄(根據(jù)Theme設(shè)置,有的布局沒有),下面的是內(nèi)容欄。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- Popout bar for action modes -->
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
我們在Activity中通過setContentView所設(shè)置的布局文件其實(shí)就是被加到內(nèi)容欄之中的,成為其唯一子View,就是上面的id為content的FrameLayout中,在代碼中可以通過content來得到對應(yīng)加載的布局:
ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
ViewGroup rootView = (ViewGroup)content.getChildAt(0);
-
還有一些其他功能:
-
作為PhoneWindow與ViewRoot之間的橋梁,ViewRoot通過DecorView設(shè)置窗口屬性。
View view = getWindow().getDecorView(); 分發(fā)ViewRoot分發(fā)來的key、touch、trackball等外部事件;
-
ViewRootImpl
ViewRootImpl可能聽起來比較陌生,但他十分重要。所有View的繪制以及事件分發(fā)等交互都是通過它來執(zhí)行或傳遞的。
但一定要注意,它不是View的子類或父類。對于結(jié)構(gòu)而言,在大部分正常情況下,一顆ViewTree的根節(jié)點(diǎn)往往是DecorView,而DecorView的根則是PhoneWindow,跟ViewRoot(ViewRootImpl)真沒什么關(guān)系。
感興趣的話,可以看一段代碼(位于WIndowManagerGolobal.java中):
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ...... ViewRootImpl root; View panelParentView = null; synchronized (mLock) { // Start watching for system property changes. ...... root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. synchronized (mLock) { final int index = findViewLocked(view, false); if (index >= 0) { removeViewLocked(index, true); } } throw e; } }可以發(fā)現(xiàn),ViewRootImpl是在Framework層的WindowManagerGlobal中初始化的,其中包含了三個(gè)ArrayList(在舊版本中實(shí)現(xiàn)是數(shù)組),其中mViews代表DecorView,mRoots代表ViewRoot。
從這里可以看出,對于應(yīng)用層來說,ViewRootImpl實(shí)際上和DecorView顯然沒有像ViewTree里父子節(jié)點(diǎn)那種包含關(guān)系。而后面的root.setView(view, wparams, panelParentView),更是證明了,DecorView的parentView也不是ViewRoot,而實(shí)際上是PhoneWindow。
實(shí)際上,ViewRoot的真正的作用其實(shí)是作為一個(gè)DecorView的“管理者”,它本身并不是一個(gè)視圖節(jié)點(diǎn),或許被叫作ViewTreeManager才更為合適,本質(zhì)上是一個(gè)管理類。ViewRootImpl還要負(fù)責(zé)處理應(yīng)用層與底層WindowManagerService交互事件
3. 從setContentView講起
接下我們就從一個(gè)常見的方法中去認(rèn)知之前提到這些類之間的關(guān)系,那就是activity里面的setContentView,是我們平常把布局內(nèi)容顯示到界面上的一個(gè)方法:activity.setContentView
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
里面方法調(diào)用了getWindow().setContentView,而這個(gè)getWindow方法獲取的就是Activity上的Window(phoneWindow)
/**
* Retrieve the current {@link android.view.Window} for the activity.
* This can be used to directly access parts of the Window API that
* are not available through Activity/Screen.
*
* @return Window The current window, or null if the activity is not
* visual.
*/
public Window getWindow() {
return mWindow;
}
注釋中說的很明確,可以看到如果當(dāng)前mWindow為null的話,則表示當(dāng)前Activity不在窗口上。
之前的mWindow.setContentView,實(shí)際上調(diào)用到的是它的實(shí)現(xiàn)類方法phoneWindow.setContentView:
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
//創(chuàng)建DecorView,并添加到mContentParent上
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
//轉(zhuǎn)場動(dòng)畫
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//將要加載的資源添加到mContentParent上
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//回調(diào)通知表示完成界面加載
cb.onContentChanged();
}
}
如果當(dāng)前內(nèi)容還未放置到窗口,則此時(shí)mContentParent==null,也就是第一次調(diào)用的時(shí)候會(huì)調(diào)用installDecor方法。FEATURE_CONTENT_TRANSITIONS,則是標(biāo)記當(dāng)前內(nèi)容加載有沒有使用過渡(轉(zhuǎn)場)動(dòng)畫。如果內(nèi)容已經(jīng)加載過,并且不需要?jiǎng)赢?,則會(huì)調(diào)用removeAllViews。
添加完Content后如有設(shè)置了FEATURE_CONTENT_TRANSITIONS則添加Scene來過度啟動(dòng)。否則mLayoutInflater.inflate(layoutResID, mContentParent);將我們的資源文件通過LayoutInflater對象轉(zhuǎn)換為View樹,并且添加至mContentParent視圖中。
既然第一次啟動(dòng)會(huì)調(diào)用到installDecor,從字面上看可以知道該方法用來添加DecorView:
private void installDecor() {
if (mDecor == null) {
//調(diào)用該方法創(chuàng)建new一個(gè)DecorView
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
//一開始DecorView未加載到mContentParent,所以此時(shí)mContentParent=null
if (mContentParent == null) {
//該方法將mDecorView添加到Window上綁定布局
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
...//添加其他資源
...//設(shè)置轉(zhuǎn)場動(dòng)畫
}
}
所以過程大致是,先通過generateDecor創(chuàng)建DecorView:
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
創(chuàng)建完后再通過調(diào)用generateLayout將setContentView的內(nèi)容賦值到mContentParent,這個(gè)方法有點(diǎn)長:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//根據(jù)當(dāng)前設(shè)置的主題來加載默認(rèn)布局
TypedArray a = getWindowStyle();
//如果你在theme中設(shè)置了window_windowNoTitle,則這里會(huì)調(diào)用到,其他方法同理,
//這里是根據(jù)你在theme中的設(shè)置去設(shè)置的
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
//是否有設(shè)置全屏
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
...//省略其他加載資源
// 添加布局到DecorView,前面說到,DecorView是繼承與FrameLayout,它本身也是一個(gè)ViewGroup,而我們前面創(chuàng)建它的時(shí)候,只是調(diào)用了new DecorView,此時(shí)里面并無什么東西。而下面的步驟則是根據(jù)用戶設(shè)置的Feature來創(chuàng)建相應(yīng)的默認(rèn)布局主題。舉個(gè)例子,如果我在setContentView之前調(diào)用了requestWindowFeature(Window.FEATURE_NO_TITLE),這里則會(huì)通過getLocalFeatures來獲取你設(shè)置的feature,進(jìn)而選擇加載對應(yīng)的布局,此時(shí)則是加載沒有標(biāo)題欄的主題,對應(yīng)的就是R.layout.screen_simple
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} ... //省略其他判斷方法
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
//選擇對應(yīng)布局創(chuàng)建添加到DecorView中
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
//設(shè)置contentParent
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
總結(jié)起來就是,首先generateLayout會(huì)根據(jù)當(dāng)前用戶設(shè)置的主題去設(shè)置對應(yīng)的Feature,接著根據(jù)對應(yīng)的Feature來選擇加載對應(yīng)的布局文件,接下來通過getLocalFeatures來獲取你設(shè)置的feature,進(jìn)而選擇加載對應(yīng)的布局,這也就是為什么我們要在setContentView之前調(diào)用requesetFeature的原因。
正如我們之前給出的那個(gè)例子:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- Popout bar for action modes -->
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
“DecorView只有一個(gè)子元素為LinearLayout,代表整個(gè)Window界面,包含通知欄、標(biāo)題欄、內(nèi)容顯示欄三塊區(qū)域。”要注意FrameLayout里面的id,@android:id/content ,我們setContentView的內(nèi)容就是添加到這個(gè)FrameLayout中。
generateLayout的返回是contentParent,而它的獲取則是ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
正好對應(yīng)id為content的FrameLayout,之后我們setContentView則是添加在mContentParent上面了。
我們再回到前面的方法:
@Override
public void setContentView(int layoutResID) {
......
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//將要加載的資源添加到mContentParent上
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//回調(diào)通知表示完成界面改變
cb.onContentChanged();
}
}
此時(shí)已經(jīng)創(chuàng)建完DecorView并且獲取到mContentParent,接著就是將你setContentView的內(nèi)容添加到mContentParent中,也就是:
mLayoutInflater.inflate(layoutResID, mContentParent);
// 或者
mContentParent.addView(view, params);
// 他們本質(zhì)上是一樣的
最后調(diào)用Callback來通知界面發(fā)生改變。Callback是Window里面的一個(gè)接口,里面聲明了當(dāng)界面更改觸摸時(shí)調(diào)用的各種方法。
這里的話,我們看下onContentChanged,雖然在PhoneWindow里面并沒有看到onContentChanged的實(shí)現(xiàn)類,但我們知道Activity本身又是加載在Window上的,所以來看下Activity:
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback
{
...
}
可以看到Activity里面實(shí)現(xiàn)了Window.Callback接口,并且
public void onContentChanged() {
}
onContentChanged方法為空,所以我們可以通過重寫該方法來監(jiān)聽布局內(nèi)容的改變
總結(jié)起來就是,在調(diào)用setContentView()方法后,DecorView被初始化,用戶視圖也被掛載到DecorView上
4. DecorView的顯示和ViewRootImpl
經(jīng)過上一部分的步驟,DecorView就已經(jīng)被建立起來了。但大家應(yīng)該都知道,界面雖然經(jīng)過了setContentView()的設(shè)置,但要等到onResume()之后才對用戶可見。
Activity的生命周期大家都已經(jīng)學(xué)過,所以不做過多的介紹了。

(關(guān)于Activity啟動(dòng)和Window的綁定我放在了補(bǔ)充里,有興趣的筒子們可以自行去查看,不然感覺實(shí)在太多了...)
在我們想要開啟一個(gè)Activity的時(shí)候,ActivityThread的handleLaunchActivity()會(huì)在Handler中被調(diào)用,那我們就來看一看這個(gè)方法:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//就是在這里調(diào)用了Activity.attach(),接著調(diào)用了Activity.onCreate()和Activity.onStart()生命周期,但是由于只是初始化了mDecor,添加了布局文件,還沒有把mDecor添加到負(fù)責(zé)UI顯示的PhoneWindow中,所以這時(shí)候?qū)τ脩魜碚f,是不可見的
Activity a = performLaunchActivity(r, customIntent);
......
if (a != null) {
//這里面執(zhí)行了Activity.onResume()
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
if (!r.activity.mFinished && r.startsNotResumed) {
try {
r.activity.mCalled = false;
//執(zhí)行Activity.onPause()
mInstrumentation.callActivityOnPause(r.activity);
}
}
}
}
我們重點(diǎn)來看一下handleResumeActivity()做了什么:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
//此時(shí)Activity.onResume()已經(jīng)被調(diào)用,但界面還是不可見的
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
//decor對用戶不可見
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
//WindowManager.LayoutParams的type為TYPE_BASE_APPLICATION
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
//decor被添加進(jìn)WindowManager了,但是這個(gè)時(shí)候還是不可見的
wm.addView(decor, l);
}
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
//在這里,劃重點(diǎn)!
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
}
也就是說,其實(shí)在onResume()執(zhí)行之后,界面還是不可見的,當(dāng)我們執(zhí)行了Activity.makeVisible()方法之后,界面才對我們是可見的。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
// 將DecorView添加到WindowManager,此處十分重要
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);//DecorView可見
}
到此DecorView便可見,顯示在屏幕中。但是在這其中,wm.addView(mDecor, getWindow().getAttributes())起到了重要的作用,因?yàn)槠鋬?nèi)部創(chuàng)建了一個(gè)ViewRootImpl對象,負(fù)責(zé)繪制顯示各個(gè)子View。
下面我們具體來看addView()方法,因?yàn)閃indowManager是個(gè)接口,具體是交給WindowManagerImpl來實(shí)現(xiàn)的:
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
...
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
}
可以發(fā)現(xiàn),這里還是交給WindowManagerGlobal的addView()方法去實(shí)現(xiàn)
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
......
synchronized (mLock) {
ViewRootImpl root;
//實(shí)例化一個(gè)ViewRootImpl對象
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
......
try {
//將DecorView交給ViewRootImpl
root.setView(view, wparams, panelParentView);
}catch (RuntimeException e) {
}
}
}
記不記得我們之前說過,WindowManagerGlobal中包含了三個(gè)ArrayList,其中mViews代表DecorView,mRoots代表ViewRoot。正是這個(gè)root.setView(view, wparams, panelParentView)方法,經(jīng)過一系列調(diào)用,最終調(diào)用了performTraversals()方法。
我們來看ViewRootImpl類中的addView方法:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//將頂層視圖DecorView賦值給全局的mView
mView = view;
.............
//標(biāo)記已添加DecorView
mAdded = true;
.............
//請求布局
requestLayout();
.............
}
}
繼續(xù)跟蹤requestLayout()方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals(); // 再看這個(gè)
}
}
.......
final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); // 跟蹤
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
// 這里傳入將要執(zhí)行遍歷繪制的 runnable
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
}
}
......
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal(); // 繼續(xù)跟蹤
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
...............
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
try {
performTraversals(); // 終于找到你,還好我沒放棄?。? } finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
}
............
經(jīng)歷千辛萬苦,我們終于接近了View樹的繪制流程。這個(gè)繪制流程就是從ViewRootImpl類的performTraversals()方法開始的,這個(gè)方法主要是根據(jù)之前設(shè)置的狀態(tài),判斷是否重新計(jì)算視圖大小(measure)、是否重新放置視圖的位置(layout)、以及是否重繪 (draw):
private void performTraversals() {
...
//最外層的根視圖的widthMeasureSpec和heightMeasureSpec由來
//lp.width和lp.height在創(chuàng)建ViewGroup實(shí)例時(shí)等于MATCH_PARENT
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 執(zhí)行測量操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//執(zhí)行布局操作
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
//執(zhí)行繪制操作
performDraw();
}
performTraversals()方法有700多行,實(shí)在是太長了,這里就先舉一下最重要的三個(gè)。他會(huì)經(jīng)過一系列復(fù)雜的調(diào)用,最終繪制出View,大體如下:

那么接下來,我們就來分三個(gè)階段介紹
5. 第一階段:Measure
主體
話不多說,我們先來看performMeasure方法:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
// 追蹤這里
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
之前我們介紹過,在handleResumeActivity中,已經(jīng)將Window.mDecor(也就是DecorView)傳入了進(jìn)來。
所以,這里的mView就是該ViewRootImpl指依賴的Activity中的DecorView,也已經(jīng)介紹過DecorView是整個(gè)View樹的最頂層,可以認(rèn)為是View樹的根,因此可以通過調(diào)用mView來繪制一個(gè)Acitivity組件的UI。
可以發(fā)現(xiàn),在performMeasure方法中我們傳入了兩個(gè)參數(shù)childWidthMeasureSpec和childHeightMeasureSpec:
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
這里調(diào)用了getRootMeasureSpec()方法去獲取widthMeasureSpec和heightMeasureSpec的值,注意方法中傳入的參數(shù),其中l(wèi)p.width和lp.height在創(chuàng)建ViewGroup實(shí)例的時(shí)候就被賦值了,它們都等于MATCH_PARENT。然后看下getRootMeasureSpec()方法中的代碼:
private int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
可以看到,這里使用了MeasureSpec.makeMeasureSpec()方法來組裝一個(gè)MeasureSpec,當(dāng)rootDimension參數(shù)等于MATCH_PARENT的時(shí)候,MeasureSpec的specMode就等于EXACTLY,當(dāng)rootDimension等于WRAP_CONTENT的時(shí)候,MeasureSpec的specMode就等于AT_MOST。并且MATCH_PARENT和WRAP_CONTENT時(shí)的specSize都是等于windowSize的,這也是為什么根視圖總是會(huì)充滿全屏的。
- UNSPECIFIED:父容器不對子View有限制,子View要多大給多大,這種一般我們不會(huì)接觸到
- EXACTLY: 表示精確模式,View的大小已經(jīng)確認(rèn),為SpecSize所指定的值。
- AT_MOST:表示子View的大小不確認(rèn),指定了該子View最大可以為多少。子View可以在該范圍內(nèi)設(shè)定自己的大小。
介紹完參數(shù),我們接下來來看view.measure()方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
mPrivateFlags &= ~MEASURED_DIMENSION_SET;
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
}
// 調(diào)用onMeasure方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
throw new IllegalStateException("onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}
注意觀察,measure()這個(gè)方法是final的,因此我們無法在子類中去重寫這個(gè)方法,說明Android是不允許我們改變View的measure框架的。然后在第9行調(diào)用了onMeasure()方法,這里才是真正去測量并設(shè)置View大小的地方,默認(rèn)會(huì)調(diào)用getDefaultSize()方法來獲取視圖的大?。?/p>
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
// 建議的最小寬度和高度都是由View的Background尺寸與通過設(shè)置View的miniXXX屬性共同決定的
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
這里傳入的measureSpec是一直從measure()方法中傳遞過來的。然后調(diào)用MeasureSpec.getMode()方法可以解析出specMode,調(diào)用MeasureSpec.getSize()方法可以解析出specSize。接下來進(jìn)行判斷,如果specMode等于AT_MOST或EXACTLY就返回specSize。
之后會(huì)在onMeasure()方法中調(diào)用setMeasuredDimension()方法來設(shè)定測量出的大小,這樣一次measure過程就結(jié)束了。
當(dāng)然,一個(gè)界面的展示可能會(huì)涉及到很多次的measure,因?yàn)橐粋€(gè)布局中一般都會(huì)包含多個(gè)子視圖,每個(gè)視圖都需要經(jīng)歷一次measure過程。ViewGroup中定義了一個(gè)measureChildren()方法來去測量子視圖的大小,如下所示:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[I];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
// 這里首先會(huì)去遍歷當(dāng)前布局下的所有子視圖,然后逐個(gè)調(diào)用measureChild()方法來測量相應(yīng)子視圖的大小
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
// 獲取布局文件中定義的多種屬性
final LayoutParams lp = child.getLayoutParams();
// 計(jì)算子視圖MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
// 計(jì)算子視圖MeasureSpec
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
可以看到,在measureChild()又調(diào)用了getChildMeasureSpec()方法來去計(jì)算子視圖的MeasureSpec,計(jì)算的依據(jù)就是布局文件中定義的MATCH_PARENT、WRAP_CONTENT等值
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//獲取當(dāng)前Parent View的Mode和Size
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//獲取Parent size與padding差值(也就是Parent剩余大小),若差值小于0直接返回0
int size = Math.max(0, specSize - padding);
//定義返回值存儲變量
int resultSize = 0;
int resultMode = 0;
//依據(jù)當(dāng)前Parent的Mode進(jìn)行switch分支邏輯
switch (specMode) {
//默認(rèn)Root View的Mode就是EXACTLY
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//如果child的layout_wOrh屬性在xml或者java中給予具體大于等于0的數(shù)值
//設(shè)置child的size為真實(shí)layout_wOrh屬性值,mode為EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//如果child的layout_wOrh屬性在xml或者java中給予MATCH_PARENT
//設(shè)置child的size為size,mode為EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//如果child的layout_wOrh屬性在xml或者java中給予WRAP_CONTENT
//設(shè)置child的size為size,mode為AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
......
//其他Mode大體分支類似
}
//將mode與size通過MeasureSpec方法整合為32位整數(shù)返回
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
然后在最后調(diào)用子視圖的measure()方法,并把計(jì)算出的MeasureSpec傳遞進(jìn)去,之后的流程就和前面所介紹的一樣了。
由此可見,視圖大小的控制是由父視圖、布局文件、以及視圖本身共同完成的,父視圖會(huì)提供給子視圖參考的大小,而開發(fā)人員可以在XML文件中指定視圖的大小,然后視圖本身會(huì)對最終的大小進(jìn)行拍板。
到此為止,我們就把視圖繪制流程的第一階段分析完了。
核心
- MeasureSpec(View的內(nèi)部類)測量規(guī)格為int型,值由高2位規(guī)格模式specMode和低30位具體尺寸specSize組成。其中specMode只有三種值:
- MeasureSpec.EXACTLY:確定模式,父View希望子View的大小是確定的,由specSize決定;
- MeasureSpec.AT_MOST:最多模式,父View希望子View的大小最多是specSize指定的值;
- MeasureSpec.UNSPECIFIED:未指定模式,父View完全依據(jù)子View的設(shè)計(jì)值來決定;
- View的measure方法是final的,不允許重載,View子類只能重載onMeasure來完成自己的測量邏輯。
- 最頂層DecorView測量時(shí)的MeasureSpec是由ViewRootImpl中g(shù)etRootMeasureSpec方法確定的(LayoutParams寬高參數(shù)均為MATCH_PARENT,specMode是EXACTLY,specSize為物理屏幕大?。?/li>
- ViewGroup類提供了measureChild,measureChild和measureChildWithMargins方法,簡化了父子View的尺寸計(jì)算。
- 只要是ViewGroup的子類就必須要求LayoutParams繼承子MarginLayoutParams,否則無法使用layout_margin參數(shù)。
- View的布局大小由父View和子View共同決定。
- 使用View的getMeasuredWidth()和getMeasuredHeight()方法來獲取View測量的寬高,必須保證這兩個(gè)方法在onMeasure流程之后被調(diào)用才能返回有效值。
小補(bǔ)充1:重寫onMeasure
之前我們提到了,measure方法是final的,所以我們無法通過重寫他去修改測量方式,但onMeasure方法卻是我們可以在子類中重寫的,也就是說,如果你不想使用系統(tǒng)默認(rèn)的測量方式,可以按照自己的意愿進(jìn)行定制,比如:
public class MyView extends View {
......
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(70, 70);
}
}
這樣的話就把View默認(rèn)的測量流程覆蓋掉了,不管在布局文件中定義MyView這個(gè)視圖的大小是多少,最終在界面上顯示的大小都將會(huì)是70*70。
小補(bǔ)充2:測量帶有margin的子視圖
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//獲取子視圖的LayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//調(diào)整MeasureSpec
//通過這兩個(gè)參數(shù)以及子視圖本身的LayoutParams來共同決定子視圖的測量規(guī)格
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);
//調(diào)運(yùn)子View的measure方法,子View的measure中會(huì)回調(diào)子View的onMeasure方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
其實(shí)也很簡單
6. 第二階段:Layout
主體
measure過程結(jié)束后,視圖的大小就已經(jīng)測量好了,接下來就是layout的過程了。正如其名字所描述的一樣,這個(gè)方法是用于給視圖進(jìn)行布局的,也就是確定視圖的位置。performLayout()中會(huì)調(diào)用View.layout()方法來執(zhí)行這個(gè)過程:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
......
host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
......
}
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// 判斷視圖大小是否變化
boolean changed = setFrame(l, t, r, b);
// 需要重新layout
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}
// 跟蹤
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~LAYOUT_REQUIRED;
if (mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~FORCE_LAYOUT;
}
layout()方法接收四個(gè)參數(shù),分別代表著左、上、右、下的坐標(biāo),當(dāng)然這個(gè)坐標(biāo)是相對于當(dāng)前視圖的父視圖而言的??梢钥吹?,這里還把剛才測量出的寬度和高度傳到了layout()方法中:
在layout()方法中,首先會(huì)調(diào)用setFrame()方法來判斷視圖的大小是否發(fā)生過變化,以確定有沒有必要對當(dāng)前的視圖進(jìn)行重繪,同時(shí)還會(huì)在這里把傳遞過來的四個(gè)參數(shù)分別賦值給mLeft、mTop、mRight和mBottom這幾個(gè)變量。接下來會(huì)在調(diào)用onLayout()方法,正如onMeasure()方法中的默認(rèn)行為一樣,也許你已經(jīng)迫不及待地想知道onLayout()方法中的默認(rèn)行為是什么樣的了:
/**
* Called from layout when this view should
* assign a size and position to each of its children.
*
* Derived classes with children should override
* this method and call layout on each of
* their children.
* @param changed This is a new size or position for this view
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
居然是空的...
這是因?yàn)閛nLayout()過程是為了確定視圖在布局中所在的位置,而這個(gè)操作應(yīng)該是由布局來完成的,即父視圖決定子視圖的顯示位置。所以我們應(yīng)該看看ViewGroup中的onLayout()方法:
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
可以看到,ViewGroup中的onLayout()方法竟然是一個(gè)抽象方法,這就意味著所有ViewGroup的子類都必須重寫這個(gè)方法。像LinearLayout、RelativeLayout等布局,都是重寫了這個(gè)方法,然后在內(nèi)部按照各自的規(guī)則對子視圖進(jìn)行布局的。
這里我們用LinearLayout舉個(gè)例子:
public class LinearLayout extends ViewGroup {
......
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
}
原來還是分Vertical和Horizontal的,我們來簡單看看Vertical了解一下吧:
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
//計(jì)算父窗口推薦的子View寬度
final int width = right - left;
//計(jì)算父窗口推薦的子View右側(cè)位置
int childRight = width - mPaddingRight;
//child可使用空間大小
int childSpace = width - paddingLeft - mPaddingRight;
//通過ViewGroup的getChildCount方法獲取ViewGroup的子View個(gè)數(shù)
final int count = getVirtualChildCount();
//獲取Gravity屬性設(shè)置
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
//依據(jù)majorGravity計(jì)算childTop的位置值
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
//開始遍歷!
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
//LinearLayout中其子視圖顯示的寬和高由measure過程來決定的,因此measure過程的意義就是為layout過程提供視圖顯示范圍的參考值
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
//獲取子View的LayoutParams
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
//依據(jù)不同的absoluteGravity計(jì)算childLeft位置
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
//通過垂直排列計(jì)算調(diào)運(yùn)child的layout設(shè)置child的位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
不難看出,一般情況下layout過程會(huì)參考measure過程中計(jì)算得到的mMeasuredWidth和mMeasuredHeight來安排子View在父View中顯示的位置,但這不是必須的,measure過程得到的結(jié)果可能完全沒有實(shí)際用處,特別是對于一些自定義的ViewGroup,其子View的個(gè)數(shù)、位置和大小都是固定的,這時(shí)候我們可以忽略整個(gè)measure過程,只在layout函數(shù)中傳入的4個(gè)參數(shù)來安排每個(gè)子View的具體位置。
到此為止,我們把視圖繪制流程的第二階段也分析完了。
核心
- View.layout方法可被重載,ViewGroup.layout為final的不可重載,ViewGroup.onLayout為abstract的,子類必須重載實(shí)現(xiàn)自己的位置邏輯。
- measure操作完成后得到的是對每個(gè)View經(jīng)測量過的measuredWidth和measuredHeight,layout操作完成之后得到的是對每個(gè)View進(jìn)行位置分配后的mLeft、mTop、mRight、mBottom,這些值都是相對于父View來說的。
- 凡是layout_XXX的布局屬性基本都針對的是包含子View的ViewGroup的,當(dāng)對一個(gè)沒有父容器的View設(shè)置相關(guān)layout_XXX屬性是沒有任何意義的。
- 使用View的getWidth()和getHeight()方法來獲取View測量的寬高,必須保證這兩個(gè)方法在onLayout流程之后被調(diào)用才能返回有效值。
7. 第三階段:Draw
概述
measure和layout的過程都結(jié)束后,接下來就進(jìn)入到draw的過程了。同樣,根據(jù)名字你就能夠判斷出,在這里才真正地開始對視圖進(jìn)行繪制。ViewRoot中的代碼會(huì)繼續(xù)執(zhí)行并創(chuàng)建出一個(gè)Canvas對象,然后調(diào)用performDraw,通過調(diào)用View的draw()方法來執(zhí)行具體的繪制工作:
private void performTraversals() {
......
final Rect dirty = mDirty;
......
canvas = mSurface.lockCanvas(dirty);
......
performDraw();
......
}
private void performDraw(){
......
mView.draw(canvas);
......
}
我們來看一看重中之重的View.draw()(ViewGroup并沒有重寫View的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
......
if (!dirtyOpaque) {
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);
}
......
// 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) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
......
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
......
}
源碼注釋說(”skip step 2 & 5 if possible (common case)”)第2和5步可以跳過,所以我們接下來重點(diǎn)剩余四步:
第一步:繪制View的背景
我們來看drawBackground(canvas):
private void drawBackground(Canvas canvas) {
//獲取xml中通過android:background屬性或者代碼中setBackgroundColor()、setBackgroundResource()等方法進(jìn)行賦值的背景Drawable
final Drawable background = mBackground;
......
//根據(jù)layout過程確定的View位置來設(shè)置背景的繪制區(qū)域
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
rebuildOutline();
}
......
//調(diào)用Drawable的draw()方法來完成背景的繪制工作
background.draw(canvas);
......
}
第三步:對View內(nèi)容容進(jìn)行繪制
這里去調(diào)用了一下View的onDraw()方法(ViewGroup也沒有重寫該方法):
/**
* Implement this to do your drawing.
*
* @param canvas the canvas on which the background will be drawn
*/
protected void onDraw(Canvas canvas) {
}
可以發(fā)現(xiàn),這里又是一個(gè)空方法。因?yàn)槊總€(gè)View的內(nèi)容部分是不同的,所以需要由子類去實(shí)現(xiàn)具體的邏輯
第四步:對當(dāng)前View的所有子View進(jìn)行繪制
先來看dispatchDraw(canvas)方法:
/**
* Called by draw to draw the child views. This may be overridden
* by derived classes to gain control just before its children are drawn
* (but after its own view has been drawn).
* @param canvas the canvas on which to draw the view
*/
protected void dispatchDraw(Canvas canvas) {
}
對于空方法已經(jīng)看習(xí)慣了....其實(shí)他們的邏輯都差不多,一般都是因?yàn)榫唧w實(shí)現(xiàn)要交給子類去完成。注釋中還說到,如果View包含子View需要重寫他,所以我們來看一看ViewGroup.dispatchDraw():
@Override
protected void dispatchDraw(Canvas canvas) {
......
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
......
for (int i = 0; i < childrenCount; i++) {
......
// 這里在for循環(huán)中遍歷了每一個(gè)子View,并通過drawChild方法繪制子View的內(nèi)容
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
......
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
......
for (int i = disappearingCount; i >= 0; i--) {
......
more |= drawChild(canvas, child, drawingTime);
}
}
......
}
drawChild:
/* ViewGroup.drawChild */
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
可以看見drawChild()方法調(diào)運(yùn)了子View的draw()方法。所以說ViewGroup類已經(jīng)為我們重寫了dispatchDraw()的功能實(shí)現(xiàn),我們一般不需要重寫該方法,但可以重載父類函數(shù)實(shí)現(xiàn)具體的功能。
到此,draw過程就從父View傳遞到了子View,并重復(fù)此過程直到到達(dá)View樹的葉子節(jié)點(diǎn)。
第六步:繪制裝飾(如滑動(dòng)條)
可以看到,這里去調(diào)用了一下View的onDrawScrollBars()方法:
/**
* <p>Request the drawing of the horizontal and the vertical scrollbar. The
* scrollbars are painted only if they have been awakened first.</p>
*
* @param canvas the canvas on which to draw the scrollbars
*
* @see #awakenScrollBars(int)
*/
protected final void onDrawScrollBars(Canvas canvas) {
//繪制ScrollBars分析不是我們這篇的重點(diǎn),所以暫時(shí)不做分析
......
}
其實(shí)不管是Button也好,TextView也好,任何一個(gè)視圖都是有滾動(dòng)條的,只是一般情況下我們都沒有讓它顯示出來而已。繪制滾動(dòng)條的代碼邏輯也比較復(fù)雜,這里就不再貼出來了,因?yàn)槲覀兊闹攸c(diǎn)是第三步過程。
通過以上流程分析,相信大家已經(jīng)發(fā)現(xiàn),View是不會(huì)幫我們繪制內(nèi)容部分的,因此需要每個(gè)視圖根據(jù)想要展示的內(nèi)容來自行繪制。如果你去觀察TextView、ImageView等類的源碼,你會(huì)發(fā)現(xiàn)它們都有重寫onDraw()這個(gè)方法,并且在里面執(zhí)行了相當(dāng)不少的繪制邏輯。繪制的方式主要是借助Canvas這個(gè)類,它會(huì)作為參數(shù)傳入到onDraw()方法中,供給每個(gè)視圖使用。
到此為止,三個(gè)階段全部完成。
核心
- 如果該View是一個(gè)ViewGroup,則需要遞歸繪制其所包含的所有子View。
- View默認(rèn)不會(huì)繪制任何內(nèi)容,真正的繪制都需要自己在子類中實(shí)現(xiàn)。
- View的繪制是借助onDraw方法傳入的Canvas類來進(jìn)行的。
- 區(qū)分View動(dòng)畫和ViewGroup布局動(dòng)畫,前者指的是View自身的動(dòng)畫,可以通過setAnimation添加,后者是專門針對ViewGroup顯示內(nèi)部子視圖時(shí)設(shè)置的動(dòng)畫,可以在xml布局文件中對ViewGroup設(shè)置layoutAnimation屬性(譬如對LinearLayout設(shè)置子View在顯示時(shí)出現(xiàn)逐行、隨機(jī)、下等顯示等不同動(dòng)畫效果)。
- 在獲取畫布剪切區(qū)(每個(gè)View的draw中傳入的Canvas)時(shí)會(huì)自動(dòng)處理掉padding,子View獲取Canvas不用關(guān)注這些邏輯,只用關(guān)心如何繪制即可。
- 默認(rèn)情況下子View的ViewGroup.drawChild繪制順序和子View被添加的順序一致,但是你也可以重載ViewGroup.getChildDrawingOrder()方法提供不同順序。
小補(bǔ)充:View的requestLayout
其實(shí)在上面分析View繪制流程時(shí)或多或少都調(diào)運(yùn)到了這個(gè)方法,而且這個(gè)方法對于View來說也比較重要,所以我們接下來分析一下他:
public void requestLayout() {
......
if (mParent != null && !mParent.isLayoutRequested()) {
//由此向ViewParent請求布局
//從這個(gè)View開始向上一直requestLayout,最終到達(dá)ViewRootImpl的requestLayout
mParent.requestLayout();
}
......
}
當(dāng)我們觸發(fā)View的requestLayout時(shí)其實(shí)質(zhì)就是層層向上傳遞,直到ViewRootImpl為止,然后觸發(fā)我們之前提到的ViewRootImpl的requestLayout方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
//View調(diào)運(yùn)requestLayout最終層層上傳到ViewRootImpl后最終觸發(fā)了該方法
scheduleTraversals();
}
}
8. 補(bǔ)充:Activity啟動(dòng)與WindowManager
對于Activity的啟動(dòng)過程,是有兩種,一種是點(diǎn)擊程序進(jìn)入啟動(dòng)的Activity,另一種而是在已有的Activity中調(diào)用startActivity,啟動(dòng)期間通過Binder驅(qū)動(dòng)ActivityWindowService,ActivityThread,ApplicationThread,ActivityStack ,Activity之間進(jìn)行通信,為當(dāng)前Activity創(chuàng)建進(jìn)程分配任務(wù)棧后啟動(dòng)Activity。
這里就跳過前面很多步驟,直接到了ActivityThread.handleLaunchActivity去查看Activity的創(chuàng)建:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
// Initialize before creating the activity
WindowManagerGlobal.initialize();
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward,
...
}
可以看到 WindowManagerGlobal.initialize()通過WindowManagerGlobal創(chuàng)建了WindowManagerServer,接下來調(diào)用了performLaunchActivity:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
try { //Activity通過ClassLoader創(chuàng)建出來
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
} ...
try {
//創(chuàng)建Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
...
if (activity != null) {
//創(chuàng)建Activity所需的Context
Context appContext = createBaseContextForActivity(r, activity);
...
//將Context與Activity進(jìn)行綁定
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.pare?nt,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor);
...
//調(diào)用activity.oncreate
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
...
//調(diào)用Activity的onstart方法
activity.performStart();
//調(diào)用activitu的OnRestoreInstanceState方法進(jìn)行Window數(shù)據(jù)恢復(fù)
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
r.persistentState);
...
return activity;
}
先通過調(diào)用 activity = mInstrumentation.newActivity創(chuàng)建Activity,可以看到里面是通過ClassLoader來加載的
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
接著創(chuàng)建Activity所需的Application和Context,再調(diào)用到activity.attach:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
//ContextImpl的綁定
attachBaseContext(context);
//在當(dāng)前Activity創(chuàng)建Window
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
//為Window設(shè)置WindowManager
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
//創(chuàng)建完后通過getWindowManager就可以得到WindowManager實(shí)例
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
可以看到對應(yīng)的Window窗口也被創(chuàng)建起來,而且Window也與WindowManager綁定。而mWindow,和mWindowManager則是Activity的成員變量??梢钥吹竭@里WindiwManager的創(chuàng)建是context.getSystemService(Context.WINDOW_SERVICE)
接著創(chuàng)建WindowManager的實(shí)現(xiàn)類,我們平時(shí)在Activity中使用getWindow()和getWindowManager,就是返回對應(yīng)這兩個(gè)成員變量:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
...
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
在調(diào)用了activity.attach后創(chuàng)建了Window和WindowManager,之后調(diào)用了:
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);
}
// 可以看到里面調(diào)用了performCreate
final void performCreate(Bundle icicle) {
onCreate(icicle);
mActivityTransitionState.readState(icicle);
performCreateCommon();
}
然后就算是進(jìn)入到Activiy的生命周期了....
參考資料
以下都是我在學(xué)習(xí)和寫文檔時(shí)候參考的文章,對于一些知識點(diǎn)的講解可能更加詳細(xì):
簡析Window、Activity、DecorView以及ViewRoot之間的錯(cuò)綜關(guān)系