ViewRoot對應於ViewRootImpl類,是鏈接WindowManager和DecorView的紐帶。View的三大流程均是經過ViewRoot來完成的。在ActivityThread中,當Activity對象被建立完畢後,會將DecorView添加到Window中,同時會建立ViewRootImpl對象,並將ViewRootImpl對象和DecorView創建關聯。android
View的繪製流程從ViewRoot的performTraversals開始,通過measure、layout和draw三個過程才能夠把一個View繪製出來,其中:canvas
performTraversals會依次調用performMeasure、performLayout和performDraw三個方法,這三個方法分別完成頂級View的measure、layout和draw這三大流程。其中performMeasure中會調用measure方法,在measure方法中又會調用onMeasure方法,在onMeasure方法中則會對全部子元素進行measure過程,這樣就完成了一次measure過程;子元素會重複父容器的measure過程,如此反覆完成了整個View數的遍歷。另外兩個過程相似,大體調用流程以下圖:bash
//獲取內容欄
ViewGroup content = findViewById(R.android.id.content);
//獲取咱們設置的Viewcontext.getChildAt(0);
複製代碼
DecorView實際上是一個FrameLayout,View層的事件都先通過DecorView,而後才傳給咱們的View。ide
MeasureSpec很大程度上決定一個View的尺寸規格,測量過程當中,系統會將View的layoutParams根據父容器所施加的規則轉換成對應的MeasureSpec,再根據這個measureSpec來測量出View的寬/高。oop
MeasureSpec表明一個32位的int值,高2位爲SpecMode,低30位爲SpecSize,SpecMode是指測量模式,SpecSize是指在某種測量模式下的規格大小。佈局
MpecMode有三類;post
1.UNSPECIFIED 父容器不對View進行任何限制,要多大給多大,通常用於系統內部 2.EXACTLY 父容器檢測到View所須要的精確大小,這時候View的最終大小就是SpecSize所指定的值,對應LayoutParams中的match_parent和具體數值這兩種模式。 3.AT_MOST 父容器指定了一個可用大小即SpecSize,View的大小不能大於這個值,不一樣View實現不一樣,對應LayoutParams中的wrap_content。優化
當View採用固定寬/高的時候,無論父容器的MeasureSpec的是什麼,View的MeasureSpec都是精確模式而且其大小遵循Layoutparams的大小。動畫
當View的寬/高是match_parent時,若是他的父容器的模式是精確模式,那View也是精確模式而且大小是父容器的剩餘空間;若是父容器是最大模式,那麼View也是最大模式而且起大小不會超過父容器的剩餘空間。spa
當View的寬/高是wrap_content時,無論父容器的模式是精確仍是最大化,View的模式老是最大化而且不能超過父容器的剩餘空間。
自定義一個ListView重寫onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//從新設置高度
heightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
複製代碼
MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);這個方法就是根據傳入的大小和模式生成一個MeasureSpec類型的32位int值。用兩位來表示模式,剩下30位表示大小。 因此傳入的Integer.MAX_VALUE >> 2就是30位的最大值,模式是MeasureSpec.AT_MOST即表示子視圖最多隻能是specSize中指定的大小,最大不超過這個大小,至關於warp_content,不超過最大size的效果。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}
複製代碼
步驟一:調用onMeasure方法;判斷佈局是水平仍是豎直;根據佈局方向選擇measureVertical或者measureHorizontal方法; 步驟二:進入measureVertical方法,遍歷子元素並對每個子元素執行measureChildBeforeLayout方法,這個方法內部會調用子元素的measure方法;mTotalLength這個變量來存儲LinearLayout在豎直方向上的高度。 步驟三:當子元素測量完畢以後,LinearLayout會根據子元素的狀況來測量本身的大小,若高度採用的是match_parent或者具體值,那麼他的繪製過程和View一致,若採用warp_content,那麼它的高度是全部的子元素所佔用的高度+豎直方向上的Padding。
由於View的measure過程和Activity的生命週期不是同步進行,若是View尚未測量完畢,那麼獲取到的寬/高就是0;因此在Activity的onCreate、onStart、onResume中均沒法正確的獲取到View的寬/高信息。下面給出4種解決方法。
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);
複製代碼
(3). wrap_content:
int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec,heightMeasureSpec);
複製代碼
在View的默認實現中,View的測量寬高和最終寬高相等,只不過測量寬高造成於measure過程,最終寬高造成於layout過程。但重寫view的layout方法可使他們不相等。
經過 onDraw 方法來實現一些不規則的效果,這種效果不方便經過佈局的組合方式來達到。這種方式須要本身支持 wrap_content ,而且padding也要去進行處理。
實現自定義的佈局方式,須要合適地處理ViewGroup的測量、佈局這兩個過程,並同時處理子View的測量和佈局過程。
擴展某種已有的控件的功能,比較簡單,不須要本身去管理 wrap_content 和padding。
比較常見,實現幾種view組合一塊兒的效果。