traversal 遍歷 英 [tr??v?rs(?)l] 美 [tr??v?rs(?)l]
hierarchy 層級;等級制度 英 ['ha??rɑ?k?] 美 ['ha??rɑrki]
ViewRoot對應(yīng)于ViewRootImpl類,它是連接WindowManager和DecorView的紐帶,View的三大流程均是通過ViewRoot來完成的。在ActivityThread中,當(dāng)Activity的對象被創(chuàng)建完畢后,會將DecorView添加到Window中,同時創(chuàng)建ViewRootImpl對象,并將ViewRootImpl對象和DecorView建立關(guān)聯(lián)
View的繪制流程從ViewRoot的performTraversals 他經(jīng)過measure,layout和draw三個過程才能將一個View繪制出來。其實measure用來測量view的寬和高,layout用來確定View在父類容器中的放置位置,而draw則負責(zé)將View繪制在屏幕上
performTraversals 會依次調(diào)用performMeasure,performLayout,performDraw三個方法,這三個方法分別完成頂級View的measure,layout和draw這三大流程。其中在performMeasure中會調(diào)用measure方法,在measure方法中又會調(diào)用onMeasure方法,在onMeasure方法中會對所有子元素進行measure過程,這時候measure流程就從父容器傳遞到子元素中了,這樣就完成了一次measure過程,接著子元素會重復(fù)父類容器的measure過程,如此反復(fù)完成整個view樹的遍歷。
measure過程決定了View的寬和高,Measure完成以后,可以通過getMeasureWidth和getMeasureHeight方法來獲取View測量后的寬和高。在幾乎所有情況下都都等于view的最終寬高。layout過程決定了view的四個頂點的坐標和實際view的寬高。draw過程則決定了View的顯示,只有draw方法完成以后View的內(nèi)容才會顯示在屏幕上。
DecorView作為頂級的View,一般情況下它內(nèi)部會包含一個豎立方向的LinearLayout,這個layout分為上下兩個部分,上邊是標題欄,下邊是內(nèi)容欄(id為content的frameLayout)
4.2理解MeasureSpec
4.2.1
MeasureSpec 代表了一個32位的int值,高2位代表specMode,低30位代表SpecSize,SpecMode是指測量模式,而specSize是指某種測量模式下的規(guī)格大小
specMode有三類,每一類都有特殊含義:
UNSPECIFIED:
父容器不對View有任何限制,要多大給多大,這種狀態(tài)一般存在系統(tǒng)內(nèi)部,表示一種測量的狀態(tài)。
EXACTLY: 父容器已經(jīng)檢測出View的所需的精確大小,這時候View對應(yīng)的最終大小就是SpecSize所指定的值,他對應(yīng)于LayoutParams中的match_parent和具體的數(shù)值這兩種模式
AT_MOST
父容器指定了一個可用大小既SpecSize,View的大小不能大于這個值,他對應(yīng)于LayoutParams中的wrap_content
4.2.2
MeasureSpec和LayoutParams的對應(yīng)關(guān)系
Layoutparams需要和父容器一起才能決定View的MeasureSpec,從而進一步?jīng)Q定View的寬高,另外,對于頂級View(既DecorView)和普通View來說,MeasureSpec的轉(zhuǎn)換過程略有不同。對于DecorView,其MeasureSpec由窗口的尺寸和自身的LayoutParams來共同確定,對于普通View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams來共同決定,Measure一單確定后,onMeasure中就可以確定View的View的測試寬高
DecorView的MeasureSpec的產(chǎn)生過程根據(jù)它的LayoutParams中的寬高的參數(shù)來劃分
1.LayoutParams.MATCH_PARENT: 精確模式,大小就是窗口的大??;
2.LayoutParams.WRAP_CONTENT:最大模式,大小不定,但是不能超過窗口的小大
3.規(guī)定大小(比如100dp)精確模式,大小為LayoutParams中指定的大小
ViewGroup的measureChildWithMargins方法 會對子元素進行measure,在調(diào)用子元素的measure之前會先用過getChildMeasureSpec方法來得到子元素的MeasureSpec。子元素的MeasureSpec的創(chuàng)建與父容器的MeasureSpec和子元素本身的LayoutParams有關(guān),此外和View的Margain和padding有關(guān)系
getChildMeasureSpec主要作用是根據(jù)父容器的MeasureSpec同時結(jié)合View本身的LayoutParams來確定子元素的MeasureSpec,參數(shù)中的padding是指父容器中已占用的空間大小,因此子元素可用的大小為父容器的尺寸減去padding
getChildMeasureSpec工作原理梳理:
parentSpecMode EXACTLY AT_MOST UNSPECIFIED
childLayoutParams
dp/px EXACTLY EXACTLY EXACTLY
childSize childSize childSize
match_parent EXACTLY AT_MOST UNSPECIFIED
parentSize parentSize 0
wrap_content AT_MOST AT_MOST UNSPECIFIED
parentSize parentSize 0
4.3 View的工作流程
獲取某個View寬高的四個方法。在onCreate onResume里獲取不到view的寬高,因為View可能還沒有測量完畢
1.Activity/View # onWindowFocusChanged
onWindowFocusChanged方法的含義是:View已經(jīng)初始化完畢了。需要注意的是onWindowFocusChanged會被調(diào)用多次,當(dāng)Activity的窗口得到或者失去焦點的時候均會被調(diào)用。
2.view.post(runnable)
通過post可以將一個runnable投遞到消息隊列的尾部,然后等待Looper調(diào)用此runnable的時候,View也已經(jīng)初始化好了
3.ViewTreeObserver
可以使用ViewTreeObserver的眾多回調(diào)可以完成這個功能,比如使用OnGlobalLayoutListener這個接口,等View樹的狀態(tài)發(fā)生改變或者View樹內(nèi)部的View的可見性發(fā)生改變時,onGlobalLayout方法將被回調(diào),因此這是獲取View的寬高的一個很好的機會。需要注意的是,伴隨著View樹的狀態(tài)改變時,onGlobalLayout會被調(diào)用多次。
4.view.measure(int widthMeasureSpc,int heightMeasureSpec)
通過手動對View進行measure來得到View的寬/高。這種方法比較復(fù)雜,這里要分情況處理,根據(jù)View的LayoutParams
match_parent 無法計算出
具體數(shù)值(dp/px)
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);
warp_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)
4.3.2layout過程
layout方法的大致流程如下:首先會通過setFrame方法來設(shè)定view的四個頂點的未知,既初始化mLeft,mRight,mTop,mBottom這四個值。view的四個頂點一旦確定,那么View在父容器中的位置也就確定了,接著會調(diào)用onlayout方法,這個方法的用途是父容器確定子元素的位置,和onMeasure方法類似onlayout方法的實現(xiàn)也和具體的布局有關(guān),所以view和ViewGroup均沒有真正實現(xiàn)onlayout方法,
4.3.3draw過程
view的繪制過程遵循如下幾步:
1.繪制背景 background.draw(canvas)
2.繪制自己 (ondraw)
3.繪制children(dispatchDraw)
4.繪制裝飾(ondrawScrollbars)
4.4.自定義view
自定義view須知
1.讓View支持wrap_content
2.如果有必要,讓View支持padding
3.盡量不要在View中使用handler,因為view本身提供了post系列方法
4.view中如果有線程或者動畫,需要及時停止,參考view#onDetachedFromWindow
5.view帶有滑動嵌套情形時,需要處理好滑動沖突