Android的UI管理系統(tǒng)層級(jí)關(guān)系
如上圖所示,這就是Android的UI管理系統(tǒng)的層級(jí)關(guān)系叹俏。當(dāng)一個(gè)應(yīng)用啟動(dòng)的時(shí)候,會(huì)啟動(dòng)一個(gè)主Activity,然后Activity會(huì)創(chuàng)建出一個(gè)窗口系統(tǒng)PhoneWindow(每個(gè)Activity都會(huì)創(chuàng)建形病,是Android系統(tǒng)中最基本的窗口系統(tǒng),也是Activity與View進(jìn)行交互的接口)。每個(gè)PhoneWindowd都有一個(gè)DecorView漠吻,它是每個(gè)Activity的根布局量瓜,即為每個(gè)界面的頂級(jí)View,本質(zhì)上是一個(gè)FramLayout途乃。
View的繪制流程:
View的繪制過程是從ViewRoot的performTraversals方法開始的绍傲,在其內(nèi)部依次調(diào)用View的performMeasure,performLayout,performDraw三個(gè)方法。這三個(gè)方法分別完成頂級(jí)View的measure,layout,draw過程欺劳。其中唧取,performMeasure中會(huì)調(diào)用measure方法,而在measure方法中又會(huì)調(diào)用onMeausre()方法划提,然后在onMeaure方法中會(huì)對(duì)所有的子元素進(jìn)行measure過程枫弟,這時(shí)measure流程就從父容器傳遞到了子元素中,子元素會(huì)繼續(xù)重復(fù)父容器的measure過程鹏往,如此反復(fù)即完成了整個(gè)View樹結(jié)構(gòu)的遍歷淡诗,最終 完成View的測量過程。
同理伊履,performLayout和performDraw方法的傳遞流程和performMeasure類似韩容,但不同的是,performDraw的傳遞是在draw方法中通過dispatchDraw方法來下發(fā)的唐瀑,不過本質(zhì)上原理還是一樣的群凶。
流程圖:
View繪制三部曲
1. onMeasure()
測量單一 view 大小的入口方法是 View 類的measure方法,在該方法中會(huì)調(diào)用 View 類的onMeasure()方法:
onMeasure的流程:
getDefaultSize()的邏輯:
setMeasuredDimension()的作用: 存儲(chǔ)測量后的大泻謇薄(寬/高)
測量的三種模式:
EXACTLY
精確值模式,即當(dāng)我們?cè)诓季治募袨閂iew指定了具體的大小
例如:android:layout_width="100dp",或者當(dāng)我們將View的大小指定為充滿父布局请梢,即為match_parent時(shí),此時(shí)力穗,該View的測量模式即為EXACTLY模式毅弧。
(View的默認(rèn)測量模式為EXACTLY模式)
AT_MOST
最大值模式,此時(shí)View的尺寸不得大于父控件允許的最大尺寸即可当窗。即對(duì)應(yīng)我們給View的寬或高指定為wrap_content時(shí)够坐。
UPSPECIFIED
不指定測量模式,即父視圖沒有限制其大小崖面,子View可以是任何尺寸元咙。該模式一般用于系統(tǒng)內(nèi)部,平時(shí)的Android開發(fā)基本用不到嘶朱。
需要注意的是蛾坯,在setMeasuredDimension()方法調(diào)用之后,我們才能使用getMeasuredWidth()和getMeasuredHeight()來獲取視圖測量出的寬高疏遏,以此之前調(diào)用這兩個(gè)方法得到的值都會(huì)是0脉课。一般情況下我們都在onLayout方法中調(diào)用兩方法來獲取測量的寬和高救军。
2.View的位置確定——onLayout()
left: View左邊界距離父容器的左邊界的距離
top: View上邊界距離父容器上邊界的距離
right: View右邊界距離父容器左邊界的距離
bottom: View下邊界距離父容器上邊界的距離
在layout方法中:
在layout方法中,首先會(huì)通過setFrame方法對(duì)View的四個(gè)頂點(diǎn)的值進(jìn)行賦值倘零,即mLeft, mRight, mTop, mBottom唱遭。當(dāng)各個(gè)頂點(diǎn)的坐標(biāo)確定以后,View在ViewGroup中的位置也就確定了呈驶。接著就要調(diào)用onLayout方法用來確定子元素的位置了拷泽。而對(duì)于View的onLayout方法,這里要說的是袖瞻,它是一個(gè)空方法司致,至于為什么,估計(jì)大家應(yīng)該也能想的通聋迎,因?yàn)閛nLayouta方法就是為了確定子元素在ViewGroup中的位置脂矫,這個(gè)功能方然要有ViewGroup去實(shí)現(xiàn)了啊。而我們點(diǎn)擊進(jìn)入ViewGroup的onLayout方法霉晕,如下:
我們可以看到他是一個(gè)抽象方法:
我們就可以通過getWidth和getHeight方法來獲取View的寬和高了庭再。
注意:
getMeasuredWidth和getWidth的區(qū)別
①getMeasuredWidth方法獲得的值是setMeasuredDimension方法設(shè)置的值
它的值在measure方法運(yùn)行后就會(huì)確定
②getWidth方法獲得是layout方法中傳遞的四個(gè)參數(shù)中的mRight-mLeft
它的值是在layout方法運(yùn)行后確定的
如果有時(shí)我們刻意去重寫layout方法,并修改方法中的參數(shù)牺堰,那么就會(huì)造成二者的值不相同拄轻。
③一般情況下在onLayout方法中使用getMeasuredWidth方法
而在除onLayout方法之外的地方用getWidth方法。
3.View的繪制——onDraw()
完成了測量和位置確立伟葫,那就差把View繪制出來以讓我們看到了恨搓。這是就要開始進(jìn)入我們的繪制流程了。在調(diào)用了layout方法后筏养,接著ViewRoot會(huì)創(chuàng)建一個(gè)Canvas對(duì)象奶卓,接著調(diào)用View的draw方法來執(zhí)行具體的繪制流程。
1.第一步: 繪制背景
這一步先先判斷是否設(shè)置了背景撼玄,然后再進(jìn)行背景的繪制。而這個(gè)背景其實(shí)就是我們?cè)赬ML布局中設(shè)置的backgroud屬性,可以使圖片或者是顏色墩邀。
之后我們可以看到這個(gè)注解:skip step 2 & 5 if possible (common case)
意思就是:如果可能掌猛,跳過第2步和第5步(常見情況)
跳過第二步
2.第三步:繪制內(nèi)容
這時(shí)會(huì)調(diào)用view的onDraw方法,來進(jìn)行具體的繪制眉睹。而View的onDraw方法點(diǎn)進(jìn)去會(huì)發(fā)現(xiàn)其實(shí)是一個(gè)空方法荔茬,它需要具體的子類自己去實(shí)現(xiàn)。到底是畫圓還是畫方那就看具體的View了竹海。
3.第四步:對(duì)所有的子View進(jìn)行繪制(dispatchDraw)
這一步是調(diào)用dispatchDraw方法對(duì)其包含的所有的子View進(jìn)行繪制慕蔚。通過調(diào)用drawChild方法來調(diào)用View的draw方法來繪制子View。而如果是單純的一個(gè)子View來說斋配,其dispatchDraw方法是一個(gè)空方法孔飒。
跳過第五步
4.以上都執(zhí)行完后就會(huì)進(jìn)入到第六步灌闺,也是最后一步,這一步的作用是對(duì)視圖的滾動(dòng)條進(jìn)行繪制坏瞄。那么你可能會(huì)奇怪桂对,當(dāng)前的視圖又不一定是ListView或者ScrollView,為什么要繪制滾動(dòng)條呢鸠匀?其實(shí)不管是Button也好蕉斜,TextView也好,任何一個(gè)視圖都是有滾動(dòng)條的缀棍,只是一般情況下我們都沒有讓它顯示出來而已宅此。
通過以上流程分析,相信大家已經(jīng)知道爬范,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è)視圖使用蘸炸。