Android View 的工作流程和原理

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 的源碼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末暴心,一起剝皮案震驚了整個(gè)濱河市妓盲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌专普,老刑警劉巖悯衬,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異檀夹,居然都是意外死亡筋粗,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)炸渡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)娜亿,“玉大人,你說(shuō)我怎么就攤上這事蚌堵÷蚓觯” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵吼畏,是天一觀的道長(zhǎng)督赤。 經(jīng)常有香客問(wèn)我,道長(zhǎng)泻蚊,這世上最難降的妖魔是什么躲舌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮性雄,結(jié)果婚禮上孽糖,老公的妹妹穿的比我還像新娘。我一直安慰自己毅贮,他們只是感情好办悟,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著滩褥,像睡著了一般病蛉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,541評(píng)論 1 305
  • 那天铺然,我揣著相機(jī)與錄音俗孝,去河邊找鬼。 笑死魄健,一個(gè)胖子當(dāng)著我的面吹牛赋铝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沽瘦,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼革骨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了析恋?” 一聲冷哼從身側(cè)響起良哲,我...
    開(kāi)封第一講書(shū)人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎助隧,沒(méi)想到半個(gè)月后筑凫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡并村,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年巍实,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哩牍。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蔫浆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出姐叁,到底是詐尸還是另有隱情瓦盛,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布外潜,位于F島的核電站原环,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏处窥。R本人自食惡果不足惜嘱吗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望滔驾。 院中可真熱鬧谒麦,春花似錦、人聲如沸哆致。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)摊阀。三九已至耻蛇,卻和暖如春踪蹬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背臣咖。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工跃捣, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人夺蛇。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓疚漆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親刁赦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子娶聘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容