View的工作原理(一)——Measure


一、認識ViewRoot和DecorView

當Activity對象被創建的時候,會將DecorView添加到Window中,同時創建ViewRootImpl對象(ViewRoot對應於ViewRootImpl類),兩者互相建立關系。

通過ViewRoot調用performTranversals開始繪制View,依次通過measure、layout、draw三個過程。如圖

 

之后依次調用:performMeasure、performLayout、performDraw三個方法

二、DecorView的內容欄

 

①、上面是標題欄,下面是內容欄。 ②、通過setContentView所設置的布局文件加載到內容欄中(R.id.content) ③、如何獲得內容欄:ViewGroup content = findViewById(R.id.content); ④、如何得到我們設置的View content.getChildAt(0);

三、MeasureSpec介紹

1.performMeasure中會調用measure方法,measure方法繪制自己的寬高,onMeasure方法會對所有子元素進行measure方法,依次類推,完成整個View樹的遍歷。

2.measure完成之后,可以通過getMeasureWidth和getMeasureHeight方法獲取View測量后的寬高。

3.先要了解MeasureSpec:系統會將LayoutParams根據父容器的規則轉換成對應的MeasureSpec,之后再根據measureSpec來測量出View的寬/高。

主要屬性:SpecMode三種測量模式:

UNSPECIFIED(未指定,一般用於系統內部),父元素部隊自元素施加任何束縛,子元素可以得到任意想要的大小;

EXACTLY(完全),父元素決定自元素的確切大小,子元素將被限定在給定的邊界里而忽略它本身大小,對應LayoutParams中的match_parent和具體數值

AT_MOST(至多),子元素至多達到指定大小的值,對應LayoutParams的wrap_content

SpecSize:某測量模式下的得到的大小。

主要方法:(根據源碼,會將測量值打包,然后通過標識符解包)

1.static int getMode(int measureSpec):根據提供的測量值(格式)提取模式(上述三個模式之一) 返回值是類型

2.static int getSize(int measureSpec):根據提供的測量值(格式)提取大小值(這個大小也就是我們通常所說的大小) 返回值是大小

3.static int makeMeasureSpec(int size,int mode):根據提供的大小值和模式創建一個測量值(格式) 返回值是標識符,就是上兩個方法的measureSpec

四、使用

一般View,其MeasureSpec由父容器的MeasureSpec和自身LayoutParams決定

1、(DecoreView的measureSpec由屏幕決定)先對DecoreView進行MeasureSpec創建:根據DecoreView的LayoutParams和屏幕的大小

放入MeasureSepc.makeMeasure()中(源碼P179頁),返回的spec(應該是標識符,通過MeasureSpec的get方法來獲取放入的參數)。

 

2、ViewGroup的Measure方法

之后調用ViewGroup的measure()方法獲取ViewGroup的范圍(該方法是固定的,沒辦法繼承),然后調用onMeasure()方法(該方法ViewGroup和View都沒有重寫,所以要自己重寫該方法)。在onMeasure()中測量子View。

測量子View的方法有:

getChildAt(int index).可以拿到index上的子view。 通過getChildCount得到子view的數目,再循環遍歷出子view。測量子View的wSpec,hSpec(結合子View的LayoutParams加上parent的Measure)。接着,childView.measure(int wSpec, int hSpec); //使用子view自身的測量方法

或者調用viewGroup的測量子view的方法:‘

//某一個子view,多寬,多高, 內部加上了viewGroup的padding值、margin值和傳入的寬高wUsed、hUsed  

measureChildWithMargins(subView, intwSpec, int wUsed, int hSpec, int hUsed); 

源碼解析:詳見P180①

原理:獲取childView的LayoutParams,然后調用getChildMeasure(P180 ②)方法獲取childMeasureSpec()。

所以說該方法的主要功能,就是獲取ViewGroup的寬高范圍,和ViewGroup的padding及childView的margin,然后通過getChildMeasure()測量真實的childView的Spec

getChildMeasure()方法:

根據ViewGroup的Measure和padding相減獲取可用范圍。

之后根據parent及child的LayoutParams屬性,制定childView的MeasureSpec

表格參照如下:

 

//所有子view 都是 多寬,多高, 內部調用了measureChild方法(P186 ②)

measureChildren(int wSpec, int hSpec);作用、獲取所有子View除非View為GONE狀態,其余都調用measureChild方法

//某一個子view,多寬,多高, 內部加上了viewGroup的padding值

measureChild(subView, int wSpec, int hSpec); (P187)

 

3、一般View的measure過程

詳見源碼(P184①):一般View的onMeasure方法不是遍歷子元素(因為本身就是),作用是調用setMeasuredDimension()設置最終大小。

源碼中有兩個方法,getDefaultSize()方法:根據measureSpec決定最終的大小。

getSuggestedMinmumWidth/Height()作用:再根據設置的背景圖和minWidth/Height寬高改變View的范圍大小。

根據源碼引出wrap_content設置無效,解決方法:(P186 設置默認寬、高)

 

4、LinearLayout部分源碼探究(P188)

onMeasure()根據縱橫,選擇measure方法(這里選擇wrap_content和verital時),之后遍歷子View利用mTopLength獲取總高度,最后LinearLayout通過measure方法選定總寬高。

引出:在onCreate、onStart、onResume方法中均無法得到某個View的寬高,原因:View的measure過程和Activity的生命周期不同步執行。

所以解決方法:

(一)、重寫onWindowFocusChanges()方法,在該方法中調用getMeasureWidth/Height()方法。原因:只有當View初始化完畢才會回調該方法

作用:當Activity的窗口得到或者數去焦點都會被調用一次。

(二)、view.post(runnable)將一個runnable投遞到一個消息隊列的尾部。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com