Android中View的繪製流程

Android中View的繪製流程

View的繪製流程是從ViewRootImpl的performTraversals方法開始,它經過measure、layout和draw三個過程才能最終將一個View繪製出來。

1、measure,即測量的意思,measure方法是在View中,它是final類型,意味着android系統不允許我們重寫該方法,即ViewGroup中也找不到該方法。View在執行完measure測量方法後,我們就可以使用getMeasuredWidth()和getMeasuredHeight()獲得View的測量寬高,在之前都是0,但並不一定是View最終顯示出來的具體大小。

2、layout,最終確定View的位置,跟measure不同的是,View中的layout不是final類型,允許子類重寫,但ViewGroup中layout是final類型,不允許子類重寫。layout執行完成後,我們就可以使用getWidth和getHeight獲得View最後顯示在屏幕上的寬高了。(關於getMeasureHeight和getWidth的區別,讀者請自行查資料,這裏就不闡述了)

3、draw,最後一個步驟,把View繪製到屏幕上,draw其實可以拆分成很多步驟,首先drawBackground,繪製背景,如果有需要的話;其次繪製自己onDraw;然後調用dispatchDraw繪製子View;最後執行onDrawForeground繪製前景或着scrollbar等。

接下來我們要理解一個概念:MeasureSpec,它包含了兩個信息,測量模式(SpecMode)和測量規格(SpecSize),SpecMode有三種模式:

(1)、MeasureSpec.EXACTLY:父容器已經檢測出View所需要的精確大小,這個時候View的最終大小就是SpecSize所指定的值。它對應於LayoutParams中的match_parent和具體的數值這兩種模式。

(2)、MeasureSpec.AT_MOST:父容器指定了一個可用大小即SpecSize,View的大小不能大於這個值,具體是什麼值要看不同View的具體實現。它對應於LayoutParams中的wrap_content。

(3)、MeasureSpec.UNSPECIFIED:父容器不對View有任何限制,要多大給多大,這種情況一般用於系統內部,表示一種測量狀態,比較少用。

一般我們使用getMeasuredWidth()和getMeasuredHeight()獲得View的測量寬高的值就包含測試模式和測量規格兩個值,系統也提供了方法用於分解:
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
反之,也有組合的方法:
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
這三個方法都是屬於MeasureSpec中的靜態方法,爲什麼要了解這個概念呢,因爲我們在自定義View的時候,往往會重寫onMeasure方法,這時候你就需要對此有所理解。

View的繪製過程是先調用measure方法,對自身進行測量,該方法內部會調用onMeasure方法,onMeasure方法是在View中定義的,代碼如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
方法中就實現了設置測量後View的高寬操作,View中的measure、onMeasure方法並沒有對子View進行測量,而ViewGroup也沒有重寫onMeasure方法,該方法一般在具體的ViewGroup實現類被重寫,如LinearLayout、RelativeLayout等就重寫了onMeasure方法,一般情況下,對子View的測量是在onMeasure方法中實現的,這裏就有很多人會問:爲什麼ViewGroup不重寫該方法對子View進行測量呢,那是因爲不同ViewGroup對子View的測量方式不相同,所以並不能在ViewGroup寫死。如果我們自定義View繼承了ViewGroup並且有子View,我們必須在重寫的onMeasure的方法中對子View進行measure。

接下來會調用layout方法,layout用以確定自身的位置,方法內部會調用onLayout方法,onLayout在ViewGroup是一個抽象方法,所有繼承ViewGroup都要實現該方法,並且對子View進行佈局,如果有你有子View的話。LinearLayout、RelativeLayout等就實現了onLayout方法,如同ViewGroup沒有實現onMeasure一樣,因爲它們對子View的佈局約束不一樣,需交給具體ViewGroup去實現。

經過measure、layout後,view的最後一步就是繪製了,ViewGroup已經重寫View的dispatchDraw對子View進行分發draw,對於我們自定義View來講,一般情況是重寫onDraw來改變本身的繪製。

經過上面三個流程,View的繪製就算完成了,值得注意的是,由於佈局的複雜性,View的測量可能會調用多次。

最後,View中有一個特殊的方法setWillNotDraw,這個方法的意義是:如果一個View不需要繪製任何內容,設置這個標記位爲true後,系統會進行相應的優化。默認情況下,View沒有啓用這個標記位,但是ViewGroup默認啓用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章