ViewRoot 和 DecorView
ViewRoot
ViewRoot 對應 ViewRootImpl 類,是連接 WindowManager 和 DecorView 的紐帶。View 的繪制流程從 ViewRoot 的 performTraversals 方法開始伤柄,經(jīng)過 Measure膀跌、Layout号俐、Draw 三個過程最終將 View 繪制出來皆怕。其中 Measure 測量 View 的寬高娃殖,Layout 來確定 View 在父容器中放置的位置值戳,Draw 負責將 View 繪制在屏幕上。
performMeasure炉爆、performLayout堕虹、performDraw 這三個方法分別完成頂級 View 的 Measure、Layout叶洞、Draw 三大流程鲫凶。其中在 performMeasure 中會調(diào)用 measure 方法禀崖,在 measure 中又會調(diào)用 onMeasure 方法衩辟,在 onMeasure 中會對所有子元素進行 measure 過程,這時 measure 流程從父容器傳遞到子元素中波附,這就完成了一次 Measure 過程艺晴。接著子元素會重復父容器的這個過程,這樣進行下去就完成了整個 View 的遍歷掸屡。performLayout 和 performDraw 類似封寞。不過,在 performDraw 中的傳遞過程是在 draw 方法中通過 dispatchDraw 實現(xiàn)仅财。
Measure 完成以后狈究,可以通過 getMeasuredWidth 和 getMeasuredHeight 方法獲取測量后的寬高,在大部分的情況下等于最終的寬高(待補充)盏求。Layout 完成后抖锥,可以通過 getTop、getBottom碎罚、getLeft 和 getRight 來得到 View 四個頂點的位置磅废,并通過 getWidth 和 getHeight 方法得到最終的寬高。
DecorView
DecorView 作為頂級 View荆烈,通常內(nèi)部會包含一個豎直方向的 LinearLayout拯勉,這個 LinearLayout 分為兩個部分(與 Android 版本以及主題相關(guān))竟趾,上面為標題欄,下面為內(nèi)容欄宫峦。在 Activity 的 setContentView 方法中所設(shè)置的布局文件就是加在內(nèi)容欄中岔帽。
可以通過如下代碼得到內(nèi)容欄
ViewGroup content = (ViewGroup) findViewById(android.R.id.content);
其中,DecorView 是一個 FrameLayout斗遏。
理解 MeasureSpec
MeasureSpec 在與父容器的共同作用下決定了一個 View 的尺寸山卦。在 Measure 的過程中,系統(tǒng)會將 View 的 LayoutParams 根據(jù)父容器的規(guī)則轉(zhuǎn)換成對應的 MeasureSpec诵次,然后再根據(jù)這個 MeasureSpec 來測量出 View 的寬高账蓉。
MeasureSpec
MeasureSpec 代表一個 32 位的 int 值。其中逾一,高 2 位代表 SpecMode铸本,低 30 位代表 SpecSize。SpecMode 指測量模式遵堵,SpecSize 指在某種測量模式下的大小箱玷。
其中,MeasureSpec 提供了打包和解包的方法
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
SpecMode 有三類:
- UNSPECIFIED
- EXACTLY
- AT_MOST
在 UNSPECIFIED 中陌宿,父容器不對 View 限制锡足,要多大給多大。一般存在于系統(tǒng)內(nèi)部壳坪,表示測量的狀態(tài)舶得。
在 EXACTLY 中,父容器已經(jīng)檢測出 View 所需要的精確大小爽蝴,這時 View 的最終大小就是 SpecSize 所指定的值沐批。對應于 LayoutParams 的 match_parent 和具體的數(shù)值。
在 AT_MOST 中蝎亚,父容器指定了可用大小 SpecSize九孩,View 的大小不能超過這個值。對應于 LayoutParams 的 wrap_content发框。
MeasureSpec 和 LayoutParams 的對應關(guān)系
對于 DecorView躺彬,MeasureSpec 由窗口尺寸和自身 LayoutParams 來決定。對于普通 View梅惯,MeasureSpec 由父容器的 MeasureSpec 和自身 LayoutParams 決定宪拥。MeasureSpec 一旦確定,onMeasure 中就可以確定 View 的寬高个唧。
在 ViewRootImpl 中的 measureHierarchy 方法中有一個 getRootMeasureSpec 方法
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root View to be windowSize.
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
由上面的代碼可以得出 DecorView 中 MeasureSpec 對應于 LayoutParams 的創(chuàng)建規(guī)則:
- LayoutParams.MATCH_PARENT:EXACTLY江解,大小為窗口大小
- LayoutParams.WRAP_CONTENT:AT_MOST,大小不定徙歼,但不能超過窗口
- 固定大欣绾印:EXACTLY鳖枕,為 LayoutParams 中指定大小
普通 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);
}
可以看到宾符,在調(diào)用子元素的 measure 方法之前會調(diào)用 getChildMeasureSpec 方法來得到子元素的 MeasureSpec。其中灭翔,子元素的 MeasureSpec 與父容器的 MeasureSpec魏烫、子元素本身的 LayoutParams,以及 View 的 margin 和 padding 有關(guān)肝箱。
接著看 ViewGroup 的 getChildMeasureSpec 方法
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
可以看到哄褒,getChildMeasureSpec 方法主要作用是根據(jù)父容器的 MeasureSpec 并結(jié)合 View 本身的 LayoutParams 來確定子元素的 MeasureSpec央拖。
歸納后得到下表
可以看到门驾,在第一行,即 View 采用固定寬高時焚虱,不管父容器的 MeasureSpec 是什么骏融,View 都是精確模式且大小遵循 LayoutParams 中的大小链嘀。當 View 的寬高為 match_parent 時,View 的 MeasureSpec 模式隨著父容器的模式改變档玻,但大小都為父容器的剩余空間怀泊。當 View 為 wrap_content 時,View 總為最大模式且大小為父容器剩余空間误趴。UNSPECIFIED 主要用于系統(tǒng)內(nèi)部 Measure 情況霹琼,這里暫不關(guān)注。