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