View的工作原理
初識ViewRoot和DectorView
首先我們給出這一節(jié)總結(jié)的結(jié)論, 然后我們再從源碼中來分析這些結(jié)論
-
ViewRoot對應(yīng)于ViewRootImpl類,它是連接WIndowManager和Decorview的紐帶,View的三大流程均是通過ViewRoot來完成的。 - 在
ActivityThread中,當(dāng)Activity對象被創(chuàng)建完畢完,會將DecorView添加到Window中,同時創(chuàng)建ViewRootImpl對象,并對二者建立關(guān)聯(lián)。 -
View的繪制流程是從ViewRoot的performTraversals方法開始的,它經(jīng)過measure、layout和draw三個過程才能最總將一個View繪制出來。-
measure用來測量View的寬和高 -
layout用來確定View在父容器中的放置位置 - 而
draw則負(fù)責(zé)將View繪制在屏幕上
-
-
performTraversals()會依次調(diào)用performMeasure()、performLayout()和performDraw()三個方法。
performMeasure()中會調(diào)用measure()方法,在measure()方法中會調(diào)用onMeasure()方法,在onMeasure()方法則會對所有的子元素進(jìn)行measure()過程,這個時候measure()流程就從父容器傳遞到子元素中,這樣就完成了依次measure()過程。performLayout()與performDraw()的傳遞流程和performMeasure()是類似的。 - 在
measure()完成以后,可以通過getMeasureWidth()和getMeasureHeight()方法來獲取到View測量的寬/高,在幾乎所有的情況下它都等于VIew的最終的寬和高。 -
DecorView作為頂級View,有上下兩部分。 其實(shí)DecorView是一個FrameLayout,View層的事件都先經(jīng)過DecorView,然后才傳遞給我們的View。
performTraversals工作流程圖.png
Activity的啟動是在ActivityThread里完成的,handleLaunchActivity()會依次間接的執(zhí)行到Activity的onCreate(),onStart(),onResume()。在執(zhí)行完這些后ActivityThread會調(diào)用 WindowManager#addView(),而這個addView()最終其實(shí)是調(diào)用了WindowManagerGlobal的addView()方法,我們就從這里開始看:

WindowManager維護(hù)著所有Activity的DecorView和ViewRootImpl。這里初始化了一個ViewRootImpl,然后調(diào)用了它的setView()方法,將DevorView作為參數(shù)傳遞了進(jìn)去。所以看看 ViewRootImpl中的setView()做了什么:
在 setView() 方法里調(diào)用了 DecorView 的 assignParent() 方法,所以去看看 View 的這個方法:

所以從上面的源碼中我們可以發(fā)現(xiàn)
ViewRootImpl其實(shí)是DecorView的parent, 它其實(shí)是位于window層和DecorView中間的位置(證明了上面的第一條和第二條結(jié)論)
我們重新看回 ViewRootImpl的setView()這個方法,這個方法里還調(diào)用了一個requestLayout()方法:

那我們繼續(xù)跟進(jìn)看一下
requestLayout()里發(fā)生了什么
mChoreographer.postCallback()這個方法,傳入了三個參數(shù),第二個參數(shù)是一個Runnable對象,先來看看這個Runnable:
這個
Runnable做的事很簡單,就調(diào)用了一個方法,doTraversal():
劃重點(diǎn)!!!這里執(zhí)行了performTraversals(), 還記得我們第三條結(jié)論嗎, 那我們?nèi)タ匆幌?code>performTraversals()中執(zhí)行了什么

在
performTraversals()中執(zhí)行了performMeasure() performLayout()和performDraw()方法.證明了上面的第三條結(jié)論
- 也就是說,其實(shí)打開一個
Activity當(dāng)它的onCreate---onResume生命周期都走完后,才將它的DecorView與新建的一個ViewRootImpl對象綁定起來,同時開始安排一次遍歷View任務(wù)也就是繪制View 樹的操作等待執(zhí)行,然后將DecorView的parent設(shè)置成ViewRootImpl對象。
這也就是為什么在onCreate---onResume里獲取不到View寬高的原因,因?yàn)樵谶@個時刻ViewRootImpl甚至都還沒創(chuàng)建,更不用說是否已經(jīng)執(zhí)行過測量操作了。- 還可以得到一點(diǎn)信息是,一個
Activity界面的繪制,其實(shí)是在onResume()之后才開始的。
理解MeasureSpec
在測量過程中,系統(tǒng)會將View的LayoutParams根據(jù)父容器所施加的規(guī)則轉(zhuǎn)換成對應(yīng)的MeasureSpec,然后再根據(jù)這個measure來測量出View的寬/高
-
MeasureSpec
MesureSpec代表一個32位的int值,高2位代表SpecMode測量模式,低30位代表在該測量模式下的規(guī)格大小。設(shè)計的目的是避免過多的對象內(nèi)存分配SpecMode有三類:- UNSPECIFIED
父容器不對View有任何限制,要多大就給多大,這種情況一般用于系統(tǒng)內(nèi)部,表示一種測量的狀態(tài)。 - EXACTLY
父容器已經(jīng)檢測出View所需要的精確大小,這和時候View的最終大小就是SpecSizes所指定的值。它對應(yīng)于LayoutParams中的match_parent和具體的數(shù)值這兩種模式 - AT_MOST
父容器指定了一個可用大小即SpecSize,View的大小不能大于這個值,具體是什么值要看不同View的具體體現(xiàn)。它對應(yīng)于LayoutParams中的wrap_content.
- UNSPECIFIED
-
MeasureSpec和LayoutParams的對應(yīng)關(guān)系
-
DecorView的MeasureSpec由窗口的尺寸和其自身的LayoutParams共同決定-
LayoutParams為MATCH_PARENT時:DecorView的大小為窗口的大小,SpecMode為EXACTLY -
LayoutParams為WRAP_PARENT時:DecorView的大小不定, 最大為窗口的大小,SpecMode為AT_MOST -
LayoutParams為固定大小時:DecorView的大小為LayoutParams指定的大小,SpecMode為EXACTLY
-
-
普通View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定.-
View采用MATCH_PARENT時,View的大小為父容器的大小,不管父容器的MeasureSpec是什么,SpecMode都與父容器的SpecMode一致, -
View采用WRAP_PARENT時,View的大小為父容器的大小,不管父容器的MesureSpec是什么,SpecMode總是AT_MOST -
View采用固定寬高的時候,View的大小為LayoutParams指定的大小,不管父容器的MeasureSpec是什么,SpecMode都是EXACTLY
-
所以我們在使用自定義View時要注意處理自定義View的WRAP_PARENT
-
View的工作流程
Measure過程
-
View的Measure過程
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }Measure過程.jpg-
getSuggestedMinimumWidth()和getSuggestedMinimumHeight()返回的是View的minWidth屬性和Background寬度的最大值 -
getDefaultSize()返回的大小就是參數(shù)widthMeasureSpec或者heightMeasureSpec中的specSize,也就是View測量后的大小,絕大部分情況和View的最終大小(Layout階段確定)相同 -
setMeasuredDimension()方法會設(shè)置View的寬/高的測量值 - 直接繼承
View的自定義控件,需要重寫onMeasure()方法并且設(shè)置wrap_content時的自身大小,否則在布局中使用了wrap_content相當(dāng)于使用了match_parent
解決方法:在onMeasure()時,給View指定一個內(nèi)部寬/高,并在wrap_content時設(shè)置即可,其他情況沿用系統(tǒng)的測量值即可
-
-
ViewGroup的measure過程
-
View中是通過performTraversals() -> performMeasure() -> measure() -> onMeasure()來進(jìn)行測量的。 - 對于
ViewGroup來說,除了完成自己的measure過程之外,還會遍歷去調(diào)用所有子元素的measure方法,個個子元素再遞歸去執(zhí)行這個過程,和View不同的是,ViewGroup是一個抽象類,沒有重寫View的onMeasure()方法,提供了measureChildren()方法。
ViewGroup的measure過程 -
measure完成之后,通過getMeasureWidth/Height()方法就可以獲取View的測量寬/高,需要注意的是,在某些極端情況下,系統(tǒng)可能要多次measure才能確定最終的測量寬/高,比較好的習(xí)慣是在onLayout方法中去獲取測量寬/高或者最終寬/高。 - 如何在
Activity中獲取View的寬/高信息
View的測量過程是在onResume()后才完成的,所以在View的onResume()前調(diào)用getMeasureWidth/Height()方法不會得到View的寬高。下面給出4種解決方法。- Activity/View.onWindowFocusChanged()
onWindowFocusChanged這個方法的含義是:View已經(jīng)初始化完畢了,寬高已經(jīng)準(zhǔn)備好了,需要注意:它會被調(diào)用多次,當(dāng)Activity的窗口得到焦點(diǎn)和失去焦點(diǎn)均會被調(diào)用。 - view.post(runnable)
通過post將一個runnable投遞到消息隊(duì)列的尾部,當(dāng)Looper調(diào)用此runnable的時候,View也初始化好了 - ViewTreeObserver
使用ViewTreeObserver的眾多回調(diào)可以完成這個功能,比如OnGlobalLayoutListener這個接口,當(dāng)View樹的狀態(tài)發(fā)送改變或View樹內(nèi)部的View的可見性發(fā)生改變時,onGlobalLayout方法會被回調(diào)。需要注意的是,伴隨著View樹狀態(tài)的改變,onGlobalLayout會被回調(diào)多次。 - view.measure(int widthMeasureSpec,int heightMeasureSpec)
- match_parent
無法measure出具體的寬高,因?yàn)椴恢栏溉萜鞯氖S嗫臻g,無法測量出View的大小 - 具體的數(shù)值(dp/px)
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY); int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY); view.measure(widthMeasureSpec,heightMeasureSpec); - wrap_content
int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST); int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST); view.measure(widthMeasureSpec,heightMeasureSpec);
- match_parent
- Activity/View.onWindowFocusChanged()
-
Layout過程
在View的默認(rèn)實(shí)現(xiàn)中,View的測量寬/高和最終寬/高是相等的,測量寬/高形成于View的measure過程,而最終寬/高形成于View的layout過程
Draw過程
- 將
View繪制到屏幕上,大概的幾個步驟:- 繪制背景
background.draw(canvas) - 繪制自己
onDraw - 繪制
children(dispatchDraw) - 繪制裝飾
onDrawScrollBars
- 繪制背景
-
View的繪制過程是通過dispatchDraw()來實(shí)現(xiàn)的,它會遍歷所有子元素的draw()方法 - 如果一個
View不需要繪制任何內(nèi)容,那么設(shè)置setWillNotDraw()為true后,系統(tǒng)會進(jìn)行相應(yīng)的優(yōu)化;ViewGroup默認(rèn)為true,如果我們的自定義ViewGroup需要通過onDraw()來繪制內(nèi)容的時候,需要顯示的關(guān)閉它。
自定義View
- 直接繼承
View或ViewGroup的控件, 需要在onMeasure()中對wrap_content做特殊處理。 - 直接繼承
View的控件,如果不在draw()方法中處理padding,那么padding屬性就無法起作用。直接繼承ViewGroup的控件也需要在onMeasure()和onLayout()中考慮padding和子元素margin的影響,不然padding和子元素的margin無效。 -
View內(nèi)部提供了post系列的方法,完全可以替代Handler的作用。 -
View中有線程和動畫,需要在View的onDetachedFromWindow()中停止。


