
ViewRoot和DecorView
這是在View三大流程之前(measure, layout, draw),需要了解的概念.
ViewRoot對應(yīng)于ViewRootImpl, 它是連接WindowManager和DecorView的紐帶. View的三大流程都是通過ViewRoot來完成的. 當(dāng)一個Activity對象在ActivityThread被創(chuàng)建后. 會將DecorView添加到Window中, 同時會創(chuàng)建ViewRootImp對象, 并將ViewRootImpl對象和DecorView建立關(guān)聯(lián).
View繪制流程是從ViewRoot的PerformTraversals()開始的. 經(jīng)過三大流程才能將一個View繪制出來.
PerformTraversals()會依次調(diào)用performMeasure, performLayout, performDraw. 而前兩種內(nèi)部的調(diào)用基本一致,都是先調(diào)用measure()/layout(),然后再調(diào)用onMeasure()/onLayout()在這個方法中會對所有子元素進(jìn)行測量和繪制.依次向內(nèi)部傳遞. performDraw()有點(diǎn)不同是在draw調(diào)用的dispatchDraw().
-
measure過程: 決定了View寬高, measure后可以通過
getMeasureWidth和getMeasureHeight來獲取View的寬高. 一般情況下是最終寬高. -
layout過程: 決定了View的頂點(diǎn)坐標(biāo)和實際View的寬高. 完成后通過
getTop, getBottom, getLeft, getRight獲得四個頂點(diǎn), 通過getWidth,和getHeight獲得寬高 - draw過程: 只有draw()方法完成之后View的內(nèi)容才會顯示出來.
setContentView(R.layout.activity_inside_intercept);
((ViewGroup) getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0);
上面第一行可以說無時無刻不存在. 而下面這行在上一章說過就是獲得我們設(shè)置的布局.那DecorView布局究竟是怎么樣的, 下圖.

DecorView就是一個FrameLayout. 而一般情況下它的布局就如上面圖那樣(具體和主題有關(guān)系). 而我們經(jīng)常setContentView(xxx). 就是把我們編寫的xml的布局添加到了DecorView的android.R.id.content的控件布局中. 所以也就能說通為什么getChildAt(0)會獲得我們的的布局.
并且為什么我們用的關(guān)聯(lián)布局的方法是setContent…
總結(jié)圖:

MeasureSpec
很大程度上決定一個View的尺寸規(guī)格, 之所以不是絕對, 是因為這個過程還受父容器的影響.
理解MeasureSpec
MeasureSpec本身是一個32位的int值, 但是卻表示了兩種信息.
- 高2位: 代表了SpecMode, 測量模式
- 低30位: 代表了SpecSize, 在上述測量模式中的大小
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
.....
}
是不是挺有意思. 三種類型分別高二位01, 00, 10來代表. 直接利用位運(yùn)算. 來實現(xiàn)可以讓頻繁計算的東西使用最接近計算機(jī)的運(yùn)算方式. 不需要額外的轉(zhuǎn)換. 也避免了過多的對象內(nèi)存分配.
說一下SpecMode的三種模式
- UNSPECIFIED: 父容器不對View有任何的限制,要多大就給多大, 這種情況一般用于系統(tǒng)內(nèi)部,表示一中測量狀態(tài)
-
EXACTLY: 父容器已經(jīng)檢測出View所需要的精確大小, 這個時候View的最終大小就是SpecSize所指定的值. 對應(yīng)著LayoutParams中的
match_parent和具體的數(shù)值. -
AT_MOST: 父容器制定了一個可用的大小及SpecSize, View的大小不能超過這個值, 它對應(yīng)與LayoutParams中的
wrap_content

MeasureSpec和LayoutParams關(guān)系
通常設(shè)置的LayoutParams,系統(tǒng)會在父容器的的約束下轉(zhuǎn)換成對應(yīng)的MeasureSpec,然后根據(jù)這個MeasureSpec來確定View測量后的寬高. 所以View自身的MeasureSpec是需要LayoutParams和父容器一起組合生成的.
上面講述的是普通View, 但是頂級View(DecorView)有所不同. DecorView是物理窗口尺寸和自身的LayoutParams決定的. 具體在ViewRootImpl類measureHierarchy()進(jìn)行生成的.
MeasureSpec一旦確定, onMeasure中就可以測量View的寬高.
對于我們?nèi)粘2僮鞯腣iew
View的measure過程是由ViewGroup傳遞而來的. 看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);
}
上面會對子元素進(jìn)行measure, 而在此之前,會通過getChildMeasureSpec()來得到子元素的MeasureSpec. 通過調(diào)用方法傳入的參數(shù)看到. 生成View的MeasureSpec和父容器的MeasureSpec, View自身方向的padding``margin, 和自身的LayoutParams這三個因素相關(guān)聯(lián).
而其中的getChildmeasureSpec()方法: 就是根據(jù)父容器的MeasureSpec同時結(jié)合View自身的LayoutParams來確定子元素的MeasureSpec.這個方法總結(jié)如下:
-
dp/px: 不管父容器的
MeasureSpec是什么. View都是EXACTLY(精確模式), 而大小遵循自身LayoutParams的大小. -
match_parent: 如果父容器是
EXACTLY(精確模式),那么子View也是EXACTLY(精確模式)并且大小是父容器的剩余空間. 如果父容器是AT_MOST(最大模式),那么子View也是AT_MOST(最大模式)并且大小不會超過父容器的剩余空間. -
wrap_content: 不管父容器是什么. View都是
AT_MOST(最大模式), 并且大小不能超過父容器剩余空間.
上述沒有說明UNSPECIFIED在match_parent和wrap_content中. 因為這個模式主要用于系統(tǒng)多次Measure的情形,一般來說不需要關(guān)注.
View的工作流程
主要指measure, layout, draw三大流程. 即測量,布局,繪制.
measure過程
這里面存在兩種場景:
- View: 通過了
measure方法就完成了測量過程 - ViewGroup: 除了測量自己,還會遍歷去調(diào)用所有子元素的
measure方法. 各個子元素在遞歸去執(zhí)行這個流程
View的measure過程
View的measure過程由其measure()方法來完成, measure()方法是一個final類型, 而在內(nèi)部調(diào)用了onMeasure()這個可不是final, 所以也可以自定義的時候復(fù)寫. 看一下內(nèi)部.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasureDimension()會設(shè)置View寬高的測量值.
這里需要看一下getDefaultSize()這個方法.
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
看到如果這個view是EXACTLY(精準(zhǔn)模式), 那么返回的大小就是SpecSize. UNSPECIFIED一般用于系統(tǒng)測量先不說. 而AT_MOST(最大模式)的時候. 雖然是不同模式但是默認(rèn)情況下和精確模式是一樣的結(jié)果.
getSuggestedMinimumWidth()和getSuggestedMinimumHeight(). 看一下實現(xiàn).
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
首先會看是否設(shè)置了背景.
-
無背景: 那么寬度為
mMinWidth,這個值對應(yīng)布局中的android:minWidth屬性,默認(rèn)為0. -
有背景: 那么取
mMinWidth和mBackground.getMinimumHeight()最大值.
而getMinimumHeight()根據(jù)看一下:
public int getMinimumHeight() {
final int intrinsicHeight = getIntrinsicHeight();
return intrinsicHeight > 0 ? intrinsicHeight : 0;
}
原來getMinimumHeight()返回的就是Drawable的原始高度. 如果沒有就返回0. 關(guān)于原始高度舉個例子ShapeDrawable無原始寬高, BitmapDrawble有原始寬高就是圖片的尺寸.
整理getDefaultSize(): 直接繼承View的自定義控件需要重寫onMeasure()方法并設(shè)置wrap_content時的自身大小,否則在布局中使用wrap_content雖然View自身的MeasureSpec的低30位保存了父容器計算自身的剩余大小. 但是在自定義的時候如果不進(jìn)行處理wrap_content,那么就會調(diào)用默認(rèn)setMeasureDimension()方法. 而默認(rèn)中方法的實參傳遞的是getDefaultSize()這個方法中對AT_MOST這種模式?jīng)]有處理. 直接沿用和精確模式的大小(相當(dāng)于設(shè)置了wrap_content卻得到了match_parent的顯示結(jié)果)
可以針對這個問題, 做出對應(yīng)的編碼進(jìn)行解決:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpaceMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpaceMode = MeasureSpec.getMode(heightMeasureSpec);
//設(shè)置兩個默認(rèn)值寬高
int defaultHeight = 100;
int defaultWidth = 100;
// 針對AT_MOST模式進(jìn)行特殊處理
if (widthSpaceMode == MeasureSpec.AT_MOST
&& heightSpaceMode == MeasureSpec.AT_MOST){
setMeasuredDimension(defaultWidth, defaultHeight);
}else if (widthSpaceMode == MeasureSpec.AT_MOST){
setMeasuredDimension(defaultWidth, heightSpaceSize);
}else if (heightSpaceMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthMeasureSpec, defaultHeight);
}
}
ViewGroup的Measure
對于
ViewGroup不光會測量自己,還會遍歷調(diào)用所有的子元素的measure(). 和View不同的是ViewGroup是一個抽象類,它沒有重寫onMeasure,但提供了measureChildren()的方法.
這個measureChildren()方法內(nèi)部比較簡單就是遍歷自己的孩子然后調(diào)用->measureChild()
這個measureChild()這個方法前面貼過源碼. 就是取出子元素的LayoutParams,并調(diào)用->getChildMeasureSpec(). 通過傳入子元素的LayoutParams里面的寬高屬性, 子元素的padding和margin, 父元素當(dāng)前(當(dāng)前ViewGroup)的MeasureSpec屬性來計算出子元素的MeasureSpec最后調(diào)用->child.measure()傳入之前計算的測量規(guī)格.
ViewGroup為什么沒有定義測量的具體過程? 因為具體的測量過程需要交給子類去實現(xiàn)的. 比如LinearLayout,RelativeLayout.
看一下LinearLayout的onMeasure()是如何定義的.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
根據(jù)設(shè)置的排列方式這里分之了兩種測量方法. 稍微看一下大概輪廓,選擇measureVertical()不貼源碼了這個方法300行呢!
首先這個方法會遍歷每個子元素并執(zhí)行->measureChildBeforeLayout()方法.這個方法內(nèi)部會調(diào)用子元素的measure(), 這樣子元素會依次測量. 并且會通過mTotalLenght這個變量來存儲LinearLayout在豎直方向上的初步高度, 每測量一個就會增加. 當(dāng)子元素測量完之后,LinearLayout會測量自己的大小.
在對自己進(jìn)行測量的時候. 如果布局中的高度采用的是match_parent或者具體數(shù)值, 那么它的測量過程和View一樣,即高度為specSize. 如果布局中采用wrap_content那么高度就是所有的子元素總和但是不能超過父元素剩余空間, 還有豎直方向LinearLayout的padding. 具體可參考resolveSizeAndState()的實現(xiàn).
到這里基本上measure測量過程已經(jīng)做了比較詳細(xì)的分析. 這個過程也是三大過程中最復(fù)雜的一個. 在measure完成之后就可以通過getMeasuredWidth/Height方法獲取View的測量寬高. 但是請注意:某些極端情況下,measure可能執(zhí)行多次. 所以盡量在onLayout()方法中去獲得最終寬高.

正確獲取寬高方法
首先明確一點(diǎn):View的measure和Activity的生命周期方法不是同步執(zhí)行.所以無法保證在某個生命周期(onCreate,onStart)獲取到正確的測量寬高
- onWindowFocusChanged()
- view.post(runnable)
- ViewTreeObserve
- view.measure()
-
onWindowFocusChanged():View已經(jīng)初始化完畢,寬高已經(jīng)準(zhǔn)備好. 這里需要注意只要Activity的焦點(diǎn)發(fā)生變化此方法就會被調(diào)用.所以如果你的界面會頻繁的進(jìn)行onPause和onResume.并且里面有很多關(guān)聯(lián)依賴的方法. 那就請注意這不是一個好辦法. - 通過
post可以將一個runnable投遞到消息隊列的尾部,然后等待Looper調(diào)用此runnable的時候.View已經(jīng)初始化完畢. - 使用
ViewTreeObserver. 當(dāng)View的可見性發(fā)生了改變的時候.onGlobalLayout()將發(fā)生回調(diào).注意伴隨著View樹的狀態(tài)改變等,這個回調(diào)方法可能會被調(diào)用多次. 使用代碼如下
ViewTreeObserver viewTreeObserver = tv_main.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
tv_main.getViewTreeObserver().removeOnGlobalLayoutListener(this);
tv_main.getMeasuredHeight();
tv_main.getMeasuredWidth();
}
});
- view.measure(widthMeasureSpec, heightMeasureSpec)
也可以手動進(jìn)行測量,但是需要分情況處理.
match_parent
當(dāng)View是此屬性的時候無法使用measure(),首先使用這種方法需要的參數(shù),是通過父容器和子元素組合來生成的子元素的MeasureSpec屬性. 所以在外部我們不知道父元素的參數(shù)值得時候只能處理不需要父元素數(shù)據(jù)就可以生成子元素的MeasureSpec的模式
所以很清楚, 這個match_patch這個模式,在給其子元素構(gòu)造MeasureSpec的時候需要得值parentSize,所以得到的也是無效.
具體數(shù)值px/dx
假設(shè)這里是100px, 首先構(gòu)成寬高對應(yīng)的MeasureSpec屬性
int widthSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
tv_main.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);
tv_main.measure(widthSpec, heightSpec);
通過(1<<30)-1 可以構(gòu)成一個MeasureSpec低30位的最大值. 用理論上View能支持的最大值去構(gòu)造
關(guān)于網(wǎng)上一些在make的使用傳入UNSPECIFIED,屬于違背了內(nèi)部實現(xiàn)的規(guī)范.不用最好
關(guān)于網(wǎng)上另一種measure()直接傳入LayoutParams.WRAP_CONTENT. 其實也只有當(dāng)子元素為wrap_content和子元素為match_parent并且父元素是wrap_conetnt時會碰巧有效.
layout過程
在ViewGroup中會先通過layout()方法確定本身的位置. 然后調(diào)用onLayout()方法遍歷所有的子元素,并調(diào)用子元素的layout()方法確定子元素的位置…依次循環(huán).
提出View的layout方法, 這里抽取部分代碼
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED)
{ onLayout(changed, l, t, r, b);}
}
這樣來看,大致流程通過setFrame()方法來設(shè)定View的四個頂點(diǎn)的位置, 即mLeft,mTop,mBottom,mRight,這四個頂點(diǎn)一旦確定.當(dāng)前View的位置也就確定. 然后會調(diào)用onLayout()方法. 這個方法是確定子元素的View位置.
這里的和onMeasure()類似, onLayout()具體實現(xiàn)和具體的布局有關(guān), 所以View和ViewGroup均沒有真正實現(xiàn)onLayout()方法.
看一下LinearLayout的onLayout()源碼
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
和onMeasure()一樣分支,接下來跟進(jìn)layoutVertical()貼出主要代碼
void layoutVertical(int left, int top, int right, int bottom) {
//省略一部分...
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
//省略一部分...
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
上面代碼大體邏輯: 首先遍歷所有孩子并調(diào)用setChildFrame()來為子元素指定對應(yīng)的位置. 其中childTop會逐漸增大, 這就意味著后面的子元素會被放置在靠下的位置. 而setChildFrame()內(nèi)部僅有一行代碼, 就是調(diào)用子元素的layout()并傳入它自身應(yīng)該存放的位置.
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
而在setChildFrame()中傳入的寬高就是子元素的測量寬高.
而在子元素的layout()中通過setFrame()來設(shè)置元素的四個頂點(diǎn).
getWidth()layout中的寬 和getMeasureWidth()中的寬永遠(yuǎn)一樣么?
在一般情況下,測量measure和layout時候的值是完全一樣的. 因為layout()中接受的參數(shù)就是通過測量的結(jié)果獲取到的. 并且內(nèi)部直接通過setFrame()賦值到自己的四個成員變量上. 但是如果對layout()進(jìn)行了復(fù)寫.如下
@Override
protected void layout(int l, int t, int r, int b) {
super.layout( l, t+200, r, b+200);
}
如果進(jìn)行了這樣的復(fù)寫, 那么最終寬高永遠(yuǎn)會與測量的出來的值相差200.

draw過程
這個過程只是將View繪制到屏幕上面.
- 繪制背景
background.draw(canvas) - 繪制自己
onDraw() - 繪制children
dispatchDraw() - 繪制裝飾
onDrawScrollBars()
View繪制過程傳遞是通過dispatchDraw()實現(xiàn)的. 傳遞了自己的畫布. 這個方法會遍歷子元素并且調(diào)用元素的draw()
View一個特有的方法setWillNotDraw(), 這個方法是設(shè)置了true那么系統(tǒng)會進(jìn)行相應(yīng)的優(yōu)化. 在View中默認(rèn)是關(guān)閉的. 而ViewGroup默認(rèn)是開啟的. 如果我們繼承了自定義ViewGroup如果還需要繪制自己的內(nèi)容那么需要顯示的關(guān)閉此標(biāo)記.

參看文章
《Android 開發(fā)藝術(shù)探索》書集
《Android 開發(fā)藝術(shù)探索》 04-View的工作原理
View的繪制-measure流程詳解
View的繪制-layout流程詳解
View的繪制-draw流程詳解