View的工作原理

ViewRoot和DecorView

在正式了解View的三大流程(measure唉铜,layout节值,draw)之前鸡捐,我們先認(rèn)識(shí)以下ViewRoot和DecorView

ViewRoot對(duì)應(yīng)于ViewRootImpl類鞠鲜,它是連接WindowManager與DecorView是紐帶嗅绸,View的三大流程都是通過(guò)ViewRootImpl來(lái)完成的脾猛。在ActivityThread中,當(dāng)Activity被創(chuàng)建的時(shí)鱼鸠,會(huì)將DecorView添加到Window中猛拴,同時(shí)創(chuàng)建一個(gè)ViewRootImpl與其關(guān)聯(lián)

View的繪制流程是從ViewRoot的performTraversals方法開(kāi)始的,經(jīng)過(guò)measure(測(cè)量寬高)瞧柔,layout(布局位置)漆弄,draw(內(nèi)容繪制)

PerformTraversals方法會(huì)依次調(diào)用performMeasure,performLayout與performDraw方法造锅,這三個(gè)方法分別完成頂級(jí)View的measure撼唾,layout,draw過(guò)程哥蔚。其中performMeasure會(huì)調(diào)用measure方法完成頂級(jí)View自身的測(cè)量過(guò)程倒谷,緊接著調(diào)用onMeasure方法對(duì)所有子元素進(jìn)行測(cè)量,接著子元素重新measure過(guò)程糙箍,如此反復(fù)完成整個(gè)view樹(shù)的遍歷渤愁。同理performLayout與performDraw也是同樣的過(guò)程,只不過(guò)performDraw的傳遞過(guò)程是在draw中調(diào)用dispatchDraw方法深夯,但是本質(zhì)上都是一樣的抖格。


Measure過(guò)程決定了View的測(cè)量寬高诺苹,完成后可以通過(guò)getMeasureWidth/Height獲得測(cè)量寬高,測(cè)量寬高一般與最終寬高一致雹拄,但是也有例外情況

Layout過(guò)程決定了View的四個(gè)頂點(diǎn)的位置以及最終寬高收奔,完成后可以通過(guò)getLeft/Right/Top/Bottom獲取四個(gè)頂點(diǎn)坐標(biāo),可通過(guò)getWidth/Height獲取View的最終寬高

Draw過(guò)程是對(duì)View內(nèi)容的繪制滓玖,在draw完成后View的內(nèi)容才會(huì)最終顯示是屏幕上


DecorView是頂級(jí)View(繼承自FrameLayout)坪哄,一般情況下它內(nèi)部包含一個(gè)LinearLayout里面分為上下兩部分(具體與Android版本與主題有關(guān)),上部分為標(biāo)題欄势篡,下部分為內(nèi)容欄(FrameLayout)翩肌。SetContentView就是將布局添加到內(nèi)容欄中,其id就是android.R.id.content禁悠。那么我們可以通過(guò)如下代碼獲取ContentView

ViewGroup décor?= getWindow().getDecorView(); // get décor

View content = décor.findViewById(android.R.id); // get content


理解MeasureSpec

MeasureSpec是View進(jìn)行測(cè)量過(guò)程的“測(cè)量規(guī)格”念祭。它里面主要存儲(chǔ)32位的int值,高2位代表測(cè)量模式碍侦,低30位代表測(cè)量大小棒卷。

View$MeasureSpec#makeMeasureSpec/getMode/getSize

MeasureSpec通過(guò)將SpecMode與SpecSize打包成一個(gè)int值來(lái)避免過(guò)多的內(nèi)存消耗,并且提供了打包與解包的方法

打包:makeMeasureSpec

解包:getMode與getSize

SpecMode分為三類:

1.?UNSPECIFIED

表示父容器不對(duì)View有任何限制

2.?EXACTLY

精確值模式祝钢,表示View使用具體寬高

3.?AT_MOST

最大模式,表示View可以根據(jù)自身需求設(shè)定寬高若厚,但是不可超過(guò)當(dāng)前的可用值

MeasureSpec與LayoutParams的關(guān)系

MeasureSpec是用于定義對(duì)View的測(cè)量規(guī)范的拦英,而View的寬高屬性定義在LayoutParams中。那么在測(cè)量時(shí)系統(tǒng)會(huì)將LayoutParams中的相關(guān)屬性在父容器的約束下轉(zhuǎn)換成對(duì)應(yīng)的MeasureSpec(即View的MeasureSpec不是由LayoutParams單獨(dú)決定的测秸,是由LayoutParams與父容器的MeasureSpec共同決定)疤估。

而DecorView的MeasureSpec是由窗口尺寸與自身LayoutParams決定

一旦MeasureSpec確定后,在onMeasure就可以確定View的測(cè)量寬高

ViewRootImpl#?measureHierarchy_1214


LayoutParams中寬高參數(shù)與SpecMode

MATCH_PARENT:精確模式霎冯,大小為父容器可用大小

WRAP_CONTENT:最大模式铃拇,不可大于父容器可用大小

固定大小:精確模式沈撞,大小為屬性指定的value值


源碼分析:ViewGroup到View的Measure過(guò)程

控件的測(cè)量主要兩個(gè)情況慷荔,如果只是一個(gè)原始View,那么直接measure過(guò)程完成測(cè)量缠俺,如果是ViewGroup除了自身測(cè)量外還會(huì)執(zhí)行onMeasure遍歷所有子View的measure過(guò)程

View的measure過(guò)程

View的measure過(guò)程是由其measure方法完成显晶,measure是final方法,意味著子類無(wú)法重寫(xiě)壹士,在measure方法中會(huì)調(diào)用View的onMeasure方法

View#onMeasure

getDefaultSize方法獲取默認(rèn)大小

getSuggestedMinimumWidth方法獲取建議的最小大小


ViewGroup并沒(méi)有覆寫(xiě)View的onMeasure方法磷雇,它只是抽象的規(guī)范,這需要在具體子類中根據(jù)自身規(guī)則完成ViewGroup自身的測(cè)量

ViewGroup#measureChildren

ViewGroup#measureChild

ViewGroup?#getChildMeasureSpec


例如垂直的LinearLayout會(huì)在onMeasure中遍歷所有元素躏救,依次調(diào)用子元素的measure方法唯笙,并且通過(guò)mTotalLength存儲(chǔ)在垂直方向的所有子元素占據(jù)的高度(子元素高度(包括padding) + margin?)

總高度?=?子元素總高度?+?自身padding

測(cè)量完子元素后根據(jù)自身lp與子元素總高度決定自身的測(cè)量


獲取控件測(cè)量寬高的時(shí)機(jī)

由于View的測(cè)量過(guò)程和Activity的生命周期是不一致的,不是同步方式執(zhí)行的。即我們無(wú)法在Activity中某個(gè)生命周期時(shí)獲取View的測(cè)量寬高(因?yàn)榇藭r(shí)View可能還沒(méi)有測(cè)量結(jié)束)

方式1.Activity/View#WindowFocusChanged

當(dāng)該方法觸發(fā)時(shí)候證明View已經(jīng)初始化完畢了崩掘,這個(gè)時(shí)候就可以去獲取寬高

@Overridepublic void?onWindowFocusChanged(booleanhasFocus) {

super.onWindowFocusChanged(hasFocus);

if(hasFocus &&?mTextView!=null){

Toast.makeText(this,"mTextView.getMeasuredHeight():"+?mTextView.getMeasuredHeight(), Toast.LENGTH_SHORT).show();

}

}

方式2.view.post(runnable)

通過(guò)post將一個(gè)runnable對(duì)象投遞到消息隊(duì)列的尾部七嫌,等待Looper執(zhí)行(此時(shí)View已經(jīng)完成初始化)

mTextView.post(newRunnable() {

@Override????public voidrun() {

mWidth?=?mTextView.getMeasuredWidth(); }});

方式3.ViewTreeObserver

使用ViewTreeObserver的眾多回調(diào)均可以實(shí)現(xiàn)該功能,比如使用OnGlobalLayoutListener接口呢堰,當(dāng)View樹(shù)的狀態(tài)發(fā)生改變或者View樹(shù)內(nèi)部的View的可見(jiàn)性發(fā)現(xiàn)改變抄瑟,onGlobalLayout會(huì)被回調(diào)(多次)

ViewTreeObserver observer =?mTextView.getViewTreeObserver();

observer.addOnGlobalLayoutListener(new?ViewTreeObserver.OnGlobalLayoutListener() {

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)

@Override????public voidonGlobalLayout() {

mTextView.getViewTreeObserver().removeOnGlobalLayoutListener(this);

mWidth?=?mTextView.getMeasuredWidth();

}

});

方式4.view.measure(int widthSpec,heightSpec);

這種情況需要根據(jù)LayoutParams分情況處理

match_parent

無(wú)法使用該方式,因?yàn)榇藭r(shí)無(wú)法知道父容器剩余空間

具體的數(shù)值

int?widthSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);

int?heightSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);

mTextView.measure(widthSpec,heightSpec);

wrap_content

int?widthSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);

int?heightSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);

mTextView.measure(widthSpec,heightSpec);

View的尺寸大小是由30位二進(jìn)制表示枉疼,那么最大即為2^30-1


注意:View的measure與onMeasure以及l(fā)ayout與onLayout方法在ViewGroup中均沒(méi)有被覆寫(xiě)皮假,因?yàn)檫@四個(gè)方法只是定義了一個(gè)流程,而ViewGroup只是布局控件的統(tǒng)一父類骂维,只有在具體的ViewGroup中才會(huì)去覆寫(xiě)onMeasure與onLayout方法惹资,比如LinearLayout

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市航闺,隨后出現(xiàn)的幾起案子褪测,更是在濱河造成了極大的恐慌,老刑警劉巖潦刃,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侮措,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡乖杠,警方通過(guò)查閱死者的電腦和手機(jī)分扎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)胧洒,“玉大人畏吓,你說(shuō)我怎么就攤上這事∥缆” “怎么了菲饼?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)列赎。 經(jīng)常有香客問(wèn)我宏悦,道長(zhǎng),這世上最難降的妖魔是什么包吝? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任肛根,我火速辦了婚禮,結(jié)果婚禮上漏策,老公的妹妹穿的比我還像新娘派哲。我一直安慰自己,他們只是感情好掺喻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布芭届。 她就那樣靜靜地躺著储矩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪褂乍。 梳的紋絲不亂的頭發(fā)上持隧,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音逃片,去河邊找鬼屡拨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛褥实,可吹牛的內(nèi)容都是我干的呀狼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼损离,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼哥艇!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起僻澎,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤貌踏,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后窟勃,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體祖乳,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年秉氧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凡资。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谬运,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出垦藏,到底是詐尸還是另有隱情梆暖,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布掂骏,位于F島的核電站轰驳,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏弟灼。R本人自食惡果不足惜级解,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望田绑。 院中可真熱鬧勤哗,春花似錦、人聲如沸掩驱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至民逼,卻和暖如春泵殴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拼苍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工笑诅, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人疮鲫。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓吆你,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親棚点。 傳聞我的和親對(duì)象是個(gè)殘疾皇子早处,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355