performTraversals 會(huì)依次調(diào)用 performMeasure、performLayout 和 performDraw 三個(gè)方法煎楣,這三個(gè)方法分別完成頂級(jí) View 的 measure滴某、layout 和 draw 這三大流程狈孔,其中在 performMeasure 中會(huì)調(diào)用 measure 方法珠插,在 measure 方法中又會(huì)調(diào)用 onMeasure 方法,在 onMeasure 方法中則會(huì)對(duì)所有的子元素進(jìn)行 measure 過(guò)程渣淤,這個(gè)時(shí)候 measure 流程就從父容器傳遞到子元素中了赏寇,這樣就完成了一次 measure 過(guò)程。接著子元素會(huì)重復(fù)父容器的 measure 過(guò)程价认,如此反復(fù)就完成了整個(gè) View 樹(shù)的遍歷嗅定。同理,performLayout 和 performDraw 的傳遞流程和 performMeasure 是類似的用踩,唯一不同的是渠退,performDraw 的傳遞過(guò)程是在 draw 方法中通過(guò) dispatchDraw 來(lái)實(shí)現(xiàn)的,不過(guò)這并沒(méi)有本質(zhì)區(qū)別脐彩。
接下來(lái)結(jié)合源碼來(lái)分析這三個(gè)過(guò)程碎乃。
Measure 測(cè)量過(guò)程
這里分兩種情況,View 的測(cè)量過(guò)程和 ViewGroup 的測(cè)量過(guò)程惠奸。
View 的測(cè)量過(guò)程
View 的 測(cè)量過(guò)程由其 measure 方法來(lái)完成梅誓,源碼如下:
可以看到 measure 方法是一個(gè) final 類型的方法,這意味著子類不能重寫(xiě)此方法佛南。
在 13 行 measure 中會(huì)調(diào)用 onMeasure 方法梗掰,這個(gè)方法是測(cè)量的主要方法,繼續(xù)看 onMeasure 的實(shí)現(xiàn)
setMeasuredDimension 方法的作用是設(shè)置 View 寬和高的測(cè)量值嗅回,我們主要看 getDefaultSize 方法
是如何生成測(cè)量的尺寸及穗。
可以看到要得到測(cè)量的尺寸需要用到 MeasureSpec,MeasureSpec 是什么鬼呢妈拌,敲黑板了,重點(diǎn)來(lái)了蓬蝶。
MeasureSpec 決定了 View 的測(cè)量過(guò)程尘分。確切來(lái)說(shuō),MeasureSpec 在很大程度上決定了一個(gè) View 的尺寸規(guī)格丸氛。
來(lái)看 MeasureSpec 類的實(shí)現(xiàn)
可以看出 MeasureSpec 中有兩個(gè)主要的值培愁,SpecMode 和 SpecSize, SpecMode 是指測(cè)量模式缓窜,而 SpecSize 是指在某種測(cè)量模式下的規(guī)格大小定续。
MeasureSpec代表一個(gè)32位的int值谍咆,高2位代表SpecMode,低30位代表SpecSize
SpecMode 有三種模式:
UNSPECIFIED
不限制:父容器不對(duì) View 有任何限制私股,要多大給多大摹察,這種情況比較少見(jiàn),一般不會(huì)用到倡鲸。
EXACTLY
限制固定值:父容器已經(jīng)檢測(cè)出 View 所需要的精確大小供嚎,這個(gè)時(shí)候 View 的最終大小就是 SpecSize 所指定的值。它對(duì)應(yīng)于 LayoutParams 中的 match_parent 和具體的數(shù)值這兩種模式峭状。
AT_MOST
限制上限:父容器指定了一個(gè)可用大小即 SpecSize克滴,View 的大小不能大于這個(gè)值,具體是什么值要看不同 View 的具體實(shí)現(xiàn)优床。它對(duì)應(yīng)于 LayoutParams 中的 wrap_content劝赔。
MeasureSpec 中三個(gè)主要的方法來(lái)處理 SpecMode 和 SpecSize
makeMeasureSpec 打包 SpecMode 和 SpecSize
getMode 解析出 SpecMode
getSize 解析出 SpecSize
不知道童鞋們之前有沒(méi)有注意到 onMeasure 有兩個(gè)參數(shù) widthMeasureSpec 和 heightMeasureSpec,那這兩個(gè)值從哪來(lái)的呢胆敞,這兩個(gè)值都是由父視圖經(jīng)過(guò)計(jì)算后傳遞給子視圖的着帽,說(shuō)明父視圖會(huì)在一定程度上決定子視圖的大小,但是最外層的根視圖 也就是 DecorView 竿秆,它的 widthMeasureSpec 和 heightMeasureSpec 又是從哪里得到的呢启摄?這就需要去分析 ViewRoot 中的源碼了,在 performTraversals 方法中調(diào)了 measureHierarchy 方法來(lái)創(chuàng)建 MeasureSpec 源碼如下:
里面調(diào)用了 getRootMeasureSpec 方法生成 MeasureSpec幽钢,繼續(xù)查看 getRootMeasureSpec 源碼
通過(guò)上述代碼歉备,DecorView 的 MeasureSpec 的產(chǎn)生過(guò)程就很明確了,具體來(lái)說(shuō)其遵守如下規(guī)則匪燕,根據(jù)它的 LayoutParams 中的寬和高的參數(shù)來(lái)劃分蕾羊。
LayoutParams.MATCH_PARENT:限制固定值,大小就是窗口的大小 windowSize
LayoutParams.WRAP_CONTENT:限制上限帽驯,大小不定龟再,但是不能超過(guò)窗口的大小 windowSize
固定大小:限制固定值尼变,大小為 LayoutParams 中指定的大小 rootDimension
對(duì)于 DecorView 而言利凑, rootDimension 的值為 lp.width 和 lp.height 也就是屏幕的寬和高,所以說(shuō) 根視圖 DecorView 的大小默認(rèn)總是會(huì)充滿全屏的嫌术。那么我們使用的 View 也就是 ViewGroup 中 View 的 MeasureSpec 產(chǎn)生過(guò)程又是怎么樣的呢哀澈,在 ViewGroup 的測(cè)量過(guò)程中會(huì)具體介紹。
先回頭看 getDefaultSize 方法:
現(xiàn)在理解起來(lái)是不是很簡(jiǎn)單呢度气,如果 specMode 是 AT_MOST 或 EXACTLY 就返回 specSize割按,這也是系統(tǒng)默認(rèn)的行為。之后會(huì)在 onMeasure 方法中調(diào)用 setMeasuredDimension 方法來(lái)設(shè)定測(cè)量出的大小磷籍,這樣 View 的 measure 過(guò)程就結(jié)束了适荣,接下來(lái)看 ViewGroup 的 measure 過(guò)程现柠。
ViewGroup 的測(cè)量過(guò)程
ViewGroup中定義了一個(gè) measureChildren 方法來(lái)去測(cè)量子視圖的大小,如下所示
從上述代碼來(lái)看弛矛,除了完成自己的 measure 過(guò)程以外够吩,還會(huì)遍歷去所有在頁(yè)面顯示的子元素,
然后逐個(gè)調(diào)用 measureChild 方法來(lái)測(cè)量相應(yīng)子視圖的大小
measureChild 的實(shí)現(xiàn)如下
measureChild 的思想就是取出子元素的 LayoutParams汪诉,然后再通過(guò) getChildMeasureSpec 來(lái)創(chuàng)建子元素的 MeasureSpec废恋,接著將 MeasureSpec 直接傳遞給 View 的 measure 方法來(lái)進(jìn)行測(cè)量。
那么 ViewGroup 是如何創(chuàng)建來(lái)創(chuàng)建子元素的 MeasureSpec 呢扒寄,我們繼續(xù)看 getChildMeasureSpec 方法源碼:
上面的代碼理解起來(lái)很簡(jiǎn)單鱼鼓,為了更清晰地理解 getChildMeasureSpec 的邏輯,這里提供一個(gè)表该编,表中對(duì) getChildMeasureSpec 的工作原理進(jìn)行了梳理迄本,表中的 parentSize 是指父容器中目前可使用的大小,childSize 是子 View 的 LayoutParams 獲取的值课竣,從 measureChild 方法中可看出
表如下:
通過(guò)上表可以看出嘉赎,只要提供父容器的 MeasureSpec 和子元素的 LayoutParams,就可以快速地確定出子元素的 MeasureSpec 了于樟,有了 MeasureSpec 就可以進(jìn)一步確定出子元素測(cè)量后的大小了公条。
至此,View 和 ViewGroup 的測(cè)量過(guò)程就告一段落了迂曲。來(lái)個(gè)小結(jié)靶橱。
MeasureSpec 的模式和生成規(guī)則
MeasureSpec 中 specMode 有三種模式:
UNSPECIFIED
不限制:父容器不對(duì) View 有任何限制,要多大給多大路捧,這種情況比較少見(jiàn)关霸,一般不會(huì)用到。
EXACTLY
限制固定值:父容器已經(jīng)檢測(cè)出 View 所需要的精確大小杰扫,這個(gè)時(shí)候 View 的最終大小就是 SpecSize 所指定的值队寇。它對(duì)應(yīng)于 LayoutParams 中的 match_parent 和具體的數(shù)值這兩種模式。
AT_MOST
限制上限:父容器指定了一個(gè)可用大小即 SpecSize章姓,View 的大小不能大于這個(gè)值佳遣,具體是什么值要看不同 View 的具體實(shí)現(xiàn)。它對(duì)應(yīng)于 LayoutParams 中的 wrap_content凡伊。
生成規(guī)則:
對(duì)于普通 View零渐,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 來(lái)共同決定。
對(duì)于不同 ViewGroup 中的不同 View 生成規(guī)則參照上表窗声。
MeasureSpec 測(cè)量過(guò)程:
measure 過(guò)程主要就是從頂層父 View 向子 View 遞歸調(diào)用 view.measure 方法相恃,measure 中調(diào) onMeasure 方法的過(guò)程辜纲。
說(shuō)人話呢就是笨觅,視圖大小的控制是由父視圖拦耐、布局文件、以及視圖本身共同完成的见剩,父視圖會(huì)提供給子視圖參考的大小杀糯,而開(kāi)發(fā)人員可以在 XML 文件中指定視圖的大小,然后視圖本身會(huì)對(duì)最終的大小進(jìn)行拍板苍苞。
那么測(cè)量過(guò)后固翰,怎么獲取 View 的測(cè)量結(jié)果呢
一般情況下 View 測(cè)量大小和最終大小是一樣的,我們可以使用 getMeasuredWidth 方法和 getMeasuredHeight 方法來(lái)獲取視圖測(cè)量出的寬高羹呵,但是必須在 setMeasuredDimension 之后調(diào)用骂际,否則調(diào)用這兩個(gè)方法得到的值都會(huì)是0。為什么要說(shuō)是一般情況下是一樣的呢冈欢,在下文介紹 Layout 中會(huì)具體介紹歉铝。
Layout 布局過(guò)程
測(cè)量結(jié)束后,視圖的大小就已經(jīng)測(cè)量好了凑耻,接下來(lái)就是 Layout 布局的過(guò)程太示。上文說(shuō)過(guò) ViewRoot 的 performTraversals 方法會(huì)在 measure 結(jié)束后,執(zhí)行 performLayout 方法香浩,performLayout 方法則會(huì)調(diào)用 layout 方法開(kāi)始布局类缤,代碼如下
View 類中 layout 方法實(shí)現(xiàn)如下:
layout 方法接收四個(gè)參數(shù),分別代表著左邻吭、上餐弱、右、下的坐標(biāo)镜盯,當(dāng)然這個(gè)坐標(biāo)是相對(duì)于當(dāng)前視圖的父視圖而言的岸裙,然后會(huì)調(diào)用 setFrame 方法來(lái)設(shè)定 View 的四個(gè)頂點(diǎn)的位置,即初始化 mLeft速缆、mRight降允、mTop、mBottom 這四個(gè)值艺糜,View 的四個(gè)頂點(diǎn)一旦確定剧董,那么 View 在父容器中的位置也就確定了,接著會(huì)調(diào)用 onLayout 方法破停,這個(gè)方法的用途是父容器確定子元素的位置翅楼,和 onMeasure 方法類似
onLayout 源碼如下:
納尼,怎么是個(gè)空方法真慢,沒(méi)錯(cuò)毅臊,就是一個(gè)空方法,因?yàn)?onLayout 過(guò)程是為了確定視圖在布局中所在的位置黑界,而這個(gè)操作應(yīng)該是由布局來(lái)完成的管嬉,即父視圖決定子視圖的顯示位置皂林,我們繼續(xù)看 ViewGroup 中的 onLayout 方法
可以看到,ViewGroup 中的 onLayout 方法竟然是一個(gè)抽象方法蚯撩,這就意味著所有 ViewGroup 的子類都必須重寫(xiě)這個(gè)方法础倍。像 LinearLayout、RelativeLayout 等布局胎挎,都是重寫(xiě)了這個(gè)方法沟启,然后在內(nèi)部按照各自的規(guī)則對(duì)子視圖進(jìn)行布局的。所以呢我們?nèi)绻远x ViewGroup 那么就要重寫(xiě) onLayout 方法犹菇。
xml 中使用
顯示效果如下:
不知道童鞋們發(fā)現(xiàn)了沒(méi)德迹,我給自定義的 ViewGroup 設(shè)置了背景色,看效果貌似占滿全屏了揭芍,可是我在 xml 中設(shè)置的 wrap_content 啊浦辨,這是什么情況,我們回頭看看 ViewGroup 中 View 的 MeasureSpec 的創(chuàng)建規(guī)則
從表中可看出因?yàn)?ViewGroup 的父布局設(shè)置的 match_parent 也就是限制固定值模式沼沈,而 ViewGroup 設(shè)置的 wrap_content流酬,那么最后 ViewGroup 使用的是 父布局的大小,也就是窗口大小 parentSize列另,那么如果我們給 ViewGroup 設(shè)置固定值就會(huì)使用 我們?cè)O(shè)置的值芽腾,來(lái)改下代碼。
效果如下:
表中的其他情況页衙,建議童鞋們自己寫(xiě)下代碼摊滔,會(huì)理解的更好。
之前說(shuō)過(guò)店乐,一般情況下 View 測(cè)量大小和最終大小是一樣的艰躺,為什么呢,因?yàn)樽罱K大小在 onLayout 中確定眨八,我們來(lái)改下代碼:
顯示效果
沒(méi)錯(cuò)腺兴,onLayout 就是這么任性,所以要獲取 View 的真實(shí)大小最好在 onLayout 之后獲取廉侧。那么如何來(lái)獲取 view 的真實(shí)大小呢页响,可以通過(guò)下面的代碼來(lái)獲取
打印如下:
可以看到實(shí)際高度和測(cè)試的高度是不一樣的,因?yàn)槲覀冊(cè)?onLayout 中做了修改段誊。
因?yàn)?View 的繪制過(guò)程和 Activity 的生命周期是不同步的闰蚕,所以我們可能在 onCreate 中獲取不到值。這里提供幾種方法來(lái)獲取
1.Activity 的 onWindowFocusChanged 方法
2.view.post(runnable) 也就是我上面使用的方法
3.ViewTreeObserver 這里童鞋們搜索下就可以找到使用方法连舍,篇幅較長(zhǎng)就不舉例子了
Draw 繪制過(guò)程
確定了 View 的大小和位置后没陡,那就要開(kāi)始繪制了,Draw 過(guò)程就比較簡(jiǎn)單,它的作用是將 View 繪制到屏幕上面盼玄。View 的繪制過(guò)程遵循如下幾步:
繪制背景 background.draw (canvas)
繪制自己(onDraw)
繪制 children(dispatchDraw)
繪制裝飾(onDrawScrollBars)
View 的繪制過(guò)程的傳遞是通過(guò) dispatchDraw 實(shí)現(xiàn)的染簇,dispatchdraw 會(huì)遍歷調(diào)用所有子元素的 draw 方法,如此 draw 事件就一層一層的傳遞下去强岸。和 Layout 一樣 View 是不會(huì)幫我們繪制內(nèi)容部分的,因此需要每個(gè)視圖根據(jù)想要展示的內(nèi)容來(lái)自行繪制砾赔,重寫(xiě) onDraw 方法蝌箍。具體可參考 TextView 或者 ImageView 的源碼。