一、ViewRoot 和 DecorView
-
ViewRoot對應於ViewRootImpl 類,它是連接WIndowManager 和 DecorView的紐帶,View的三大流程均是通過VIewRoot來完成的,在ActivityThread中,當Activity對象被創建後,會將DecorView 添加到Window中,同時會創建VIewRootImpl對象,並將ViewRootImpl對象和DecorView建立關聯
-
View 的繪製流程是從ViewRoot的 performTraversals 方法開始的,performTraversals 依次調用 performMeasure、performLayout 和 performDraw,這三個方法分別完成頂級View的measure、layout 和 draw
-
performMeasure -> measure -> onMeasure,對子元素進行measure(layout 和 draw類似)
-
measure過程決定了View的寬/高,getMeasuredwidth/getMeasuredHeight,除特殊情況外它都等於View的最終寬/高;Layout過程決定View的四個頂點座標和實際寬/高,getTop/getBottom/getLeft/getRight/getWidth/getHeight;Draw決定View的顯示
-
DecorView作爲頂級View,包含上下兩個部分,上面是標題欄,下面是內容欄,Activity中 setContentView 所設置的佈局是被加到內容欄;View層的事件都先經過DecorView 然後傳遞給我們的View
二、MeasureSpec
MeasureSpec代表一個32位的int值,高 2 位代表SpecMode 測量模式,低30位代表 SpecSize 規格大小
1. SpecMode有三類
-
UNSPECIFIED:父容器不對View有任何限制,要多大有多大,一般用於系統內部
-
EXACTLY:父容器已經檢測說View所需要的的精確大小,View的最終大小就是SpecSize指定的值,對應於 match_parent 和 具體數值兩種模式
-
AT_MOST:父容器指定了一個可用大小即SpecSize,View的大小不能大於這個值,對應於 wrap_content
2. MeasureSpec 和 LayoutParams
對於頂級View(DecorView),其MeasureSpec由窗口的尺寸和其自身的LayoutParams來共同確定;
對於普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams來共同確定
3. View 的measure
View的measure過程由ViewGrou傳遞而來,ViewGroup 的 measureChildWithMargins
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
4. 注意
- 當View採用固定寬/高的時候,不管父容器的 MeasureSpec是什麼,View的MeasureSpec 都是精確模式且遵循Layoutparams中的大小
- 當View的寬/高是 match_parent 時,如果父容器是最大模式,那麼子View也是最大模式,且不超過父容器的剩餘空間
- 當View的寬/高是 wrap_content 時,不管父容器的模式是精準還是最大化,View的模式總是最大化,且不超過父容器的剩餘空間
三、View的工作流程
1. measure 過程
1)View 的 measure過程
View 的measure過程由其measure方法來完成,measure方法是一個final類型的方法,子類不能重寫,measure方法中會去調用 onMeasure->setMeasuredDimension方法會設置View 寬/高的測量值
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
2)ViewGroup 的 measure過程
ViewGroup 除了完成自己的measure,還會遍歷去調用所有子元素的measure,各個子元素再遞歸去執行這個過程。ViewGroup 是一個抽象類,沒有重寫 onMeasure,其測量過程的onMeasure 方法需要各個子類去具體實現,但提供了 measureChildren方法,ViewGroup 在measure時,會對每一個子元素進行measure
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
3)異步獲取View寬高
如果想在onCreate等方法裏獲取View的寬高,有4種方式:
- Activity/View#onWindowFocusChanged
onWindowFocusChanged 表示View已經初始化完畢,寬高已經準備好,在Activity的窗口得到和失去焦點時均會被調用
- view.post(runnable)
- ViewTreeObserver#onGlobalLayoutListener
- view.measure 手動measure
2. Layout 過程
ViewGroup的位置被確定後,onLayout中會遍歷所有的子元素並調用其 layout方法,在layout方法中 onLayout方法又會被調用;layout方法確定View 本身的位置,onLayout方法確定所有子元素的位置
View 和 ViewGroup 均沒有真正實現 onLayout
3. Draw 過程
- 繪製背景 background.draw(canvas)
- 繪製自己 (onDraw)
- 繪製 children (dispatchDraw)
- 繪製裝飾(onDrawScrollBars)