一、測(cè)量過(guò)程的信使 - MeasureSpec
因?yàn)闇y(cè)量是一個(gè)從上到下的過(guò)程呐萌,而在這個(gè)過(guò)程當(dāng)中渗磅,父容器有必要告訴子View
它的一些繪制要求,那么這時(shí)候就需要依賴一個(gè)信使按灶,來(lái)傳遞這個(gè)要求症革,它就是MeasureSpec
.
MeasureSpec
是一個(gè)32
位的int
類型,我們把它分為高2
位和低30
位鸯旁。
其中高2
位表示mode
地沮,它的取值為:
UNSPECIFIED(0) : The parent has not imposed any constraint on the child. It can be whatever size it wants.
EXACTLY(1) : The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.
AT_MOST(2) : The child can be as large as it wants up to the specified size.
低30
位表示具體的size
。
MeasureSpec
是父容器傳遞給子View
的寬高要求羡亩,并不是說(shuō)它傳遞的size
是多大摩疑,子View
最終就是多大,它是根據(jù)父容器的MeasureSpec
和子View
的LayoutParams
共同計(jì)算出來(lái)的畏铆。
為了更好的理解上面這段話雷袋,我們需要借助ViewGroup
中的兩個(gè)函數(shù):
measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)
getChildMeasureSpec(int spec, int padding, int childDimension)
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);
}
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) {
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
可以看到,在調(diào)用getChildMeasureSpec
之前辞居,需要考慮parent
和child
之間的間距楷怒,這包括parent
的padding
和child
的margin
,因此瓦灶,參與傳遞給child
的MeasureSpec
的參數(shù)要考慮這么幾方面:
- 父容器的
measureSpec
和padding
- 子
View
的height
和widht
以及margin
鸠删。
下面我們來(lái)分析getChildMeasureSpec
的具體流程,它對(duì)寬高的處理邏輯都是相同的贼陶,根據(jù)父容器measureSpec
的mode
刃泡,分成以下幾種情況:
1.1 父容器的mode
為EXACTLY
這種情況下說(shuō)明父容器的大小已經(jīng)確定了巧娱,就是固定的值。
- 子
View
指定了大小
那么子View
的mode
就是EXACTLY
烘贴,size
就是布局里面的值禁添,這里就有疑問(wèn)了,子View
所指定的寬高大于父容器的寬高怎么辦呢桨踪?老翘,我們先留著這個(gè)疑問(wèn)。 - 子
View
為MATCH_PARENT
子View
希望和父容器一樣大锻离,因?yàn)楦溉萜鞯拇笮∈谴_定的铺峭,所以子View
的大小也是確定的,size
就是父容器measureSpec
的size
- 父容器的padding
- 子View``margin
汽纠。 - 子
View
為WRAP_CONTENT
子容器只要求能夠包裹自己的內(nèi)容逛薇,但是這時(shí)候它又不知道它所包裹的內(nèi)容到底是多大,那么這時(shí)候它就指定自己的大小就不能超過(guò)父容器的大小疏虫,所以mode
為AT_MOST
永罚,size
和上面類似。
1.2 父容器的mode
為AT_MOST
在這種情況下卧秘,父容器說(shuō)明了自己最多不能超過(guò)多大呢袱,數(shù)值在measureSpec
的size
當(dāng)中:
- 子
View
指定大小
同上分析。 - 子
View
為MATCH_PARENT
子View
希望和父容器一樣大翅敌,而此時(shí)父容器只知道自己不能超過(guò)多大羞福,因此子View
也就只能知道自己不能超過(guò)多大,所以它的mode
為AT_MOST
蚯涮,size
就是父容器measureSpec
的size
- 父容器的padding
- 子View``margin
治专。 - 子
View
為WRAP_CONTENT
子容器只要求能夠包裹自己的內(nèi)容,但是這時(shí)候它又不知道它所包裹的內(nèi)容到底是多大遭顶,這時(shí)候雖然父容器沒(méi)有指定大小张峰,但是它指定了最多不能超過(guò)多少,這時(shí)候子View
也不能超過(guò)這個(gè)值棒旗,所以mode
為AT_MOST
喘批,size
的計(jì)算和上面類似。
1.3 父容器的mode
為UNSPECIFIED
- 子
View
指定大小
同上分析铣揉。 - 子
View
為MATCH_PARENT
子View
希望和父容器一樣大饶深,但是這時(shí)候父容器并沒(méi)有約束,所以子View
也是沒(méi)有約束的逛拱,所以它的mode
也為UNSPECIFIED
敌厘,size
的計(jì)算和之前一致。 - 子
View
為WRAP_CONTENT
子View
不知道它包裹的內(nèi)容多大朽合,并且父容器是沒(méi)有約束的俱两,那么也只能為UNSPECIFIED
了饱狂,size
的計(jì)算和之前一致。
二锋华、測(cè)量過(guò)程的起點(diǎn) - performTraversals()
介紹完了基礎(chǔ)的知識(shí)嗡官,我們來(lái)從起點(diǎn)來(lái)整個(gè)看一下從View
樹(shù)的根節(jié)點(diǎn)到葉節(jié)點(diǎn)的整個(gè)測(cè)量的過(guò)程箭窜。
我們先直接說(shuō)明結(jié)論毯焕,整個(gè)測(cè)量的起點(diǎn)是在ViewRootImpl
的performTraversals()
當(dāng)中:
private void performTraversals() {
......
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//...
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
上面的mView
是通過(guò)setView(View view, WindowManager.LayoutParams attrs, View panelParentView)
傳進(jìn)來(lái)的,那么這個(gè)view
是什么時(shí)候傳遞進(jìn)來(lái)的呢磺樱?
現(xiàn)在回憶一下纳猫,在ActivityThread
的handleResumeActivity
中,我們調(diào)用了ViewManager.add(mDecorView, xxx)
竹捉,而這個(gè)方法最終會(huì)調(diào)用到WindowManagerGlobal
的下面這個(gè)方法:
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
root = new ViewRootImpl(view.getContext(), display);
}
//....
root.setView(view, wparams, panelParentView);
}
也就是說(shuō)芜辕,上面的mView
也就是我們?cè)?code>setContentView當(dāng)中渲染出來(lái)的mDecorView
,也就是說(shuō)它是整個(gè)View
樹(shù)的根節(jié)點(diǎn)块差,因?yàn)?code>mDecorView是一個(gè)FrameLayout
侵续,所以它調(diào)用的是FrameLayout
的measure
方法。
那么這整個(gè)從根節(jié)點(diǎn)遍歷完整個(gè)View
樹(shù)的過(guò)程是怎么實(shí)現(xiàn)的呢憨闰?
它其實(shí)就是依賴于measure
和onMeasure
:
- 對(duì)于
View
状蜗,measure
是在它里面定義的,而且它是一個(gè)final
方法鹉动,因此它的所有子類都沒(méi)有辦法重寫該方法轧坎,在該方法當(dāng)中,會(huì)調(diào)用onMeasure
來(lái)設(shè)置最終測(cè)量的結(jié)果泽示,對(duì)于View
來(lái)說(shuō)开仰,它只是簡(jiǎn)單的取出父容器傳進(jìn)來(lái)的要求來(lái)設(shè)置欧募,并沒(méi)有復(fù)雜的邏輯。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
- 對(duì)于
ViewGroup
,由于它是View
的子類奶稠,因此它不可能重寫measure
方法,并且它也沒(méi)有重寫onMeasure
方法钠导。 - 對(duì)于繼承于
View
的控件种柑,例如TextView
,它會(huì)重寫onMeasure
定欧,與View#onMeasure
不同的是渔呵,它會(huì)考慮更多的情況來(lái)決定最終的測(cè)量結(jié)果。 - 對(duì)于繼承于
ViewGroup
的控件砍鸠,例如FrameLayout
扩氢,它同樣會(huì)重寫onMeasure
方法,與繼承于View
的控件不同的是爷辱,由于ViewGroup
可能會(huì)有子View
录豺,因此它在設(shè)置自己最終的測(cè)量結(jié)果之前朦肘,還有一個(gè)重要的任務(wù):調(diào)用子View
的measure
方法,來(lái)對(duì)子View
進(jìn)行測(cè)量双饥,并根據(jù)子View
的結(jié)果來(lái)決定自己的大小媒抠。
因此,整個(gè)從上到下的測(cè)量咏花,其實(shí)就是一個(gè)View
樹(shù)節(jié)點(diǎn)的遍歷過(guò)程趴生,每個(gè)節(jié)點(diǎn)的onMeasure
返回時(shí),就標(biāo)志它的測(cè)量結(jié)束了昏翰,而這整個(gè)的過(guò)程是以View
中measure
方法為紐帶的:
- 整個(gè)過(guò)程的起點(diǎn)是
mDecorView
這個(gè)根節(jié)點(diǎn)的measure
方法苍匆,也就是performTraversals
中的那句話。 - 如果節(jié)點(diǎn)有子節(jié)點(diǎn)棚菊,也就是說(shuō)它是繼承于
ViewGroup
的控件浸踩,那么在它的onMeasure
方法中,它并不會(huì)直接調(diào)用子節(jié)點(diǎn)的onMeasure
方法统求,而是通過(guò)調(diào)用子節(jié)點(diǎn)measure
方法检碗,由于子節(jié)點(diǎn)不可能重寫View#measure
方法,因此它最終是通過(guò)View#measure
來(lái)調(diào)用子節(jié)點(diǎn)重寫的onMeasure
來(lái)進(jìn)行測(cè)量码邻,子節(jié)點(diǎn)再在其中進(jìn)行響應(yīng)的邏輯處理折剃。 - 如果節(jié)點(diǎn)沒(méi)有子節(jié)點(diǎn),那么當(dāng)它的
onMeausre
方法被調(diào)用時(shí)冒滩,它需要設(shè)置好自己的測(cè)量結(jié)果就行了微驶。
對(duì)于measure
和onMeasure
的區(qū)別,我們可以用一句簡(jiǎn)單的話來(lái)總結(jié)一下:measure
負(fù)責(zé)進(jìn)行測(cè)量的傳遞开睡,onMeasure
負(fù)責(zé)測(cè)量的具體實(shí)現(xiàn)因苹。
三、測(cè)量過(guò)程的終點(diǎn) - onMeasure
當(dāng)中的setMeasuredDimension
上面我們講到設(shè)置的測(cè)量結(jié)果篇恒,其實(shí)測(cè)量過(guò)程的最終目的是:通過(guò)調(diào)用setMeasuredDimension
方法來(lái)給mMeasureHeight
和mMeasureWidth
賦值扶檐。
只要上面這個(gè)過(guò)程完成了,那么該ViewGroup/View/及其實(shí)現(xiàn)類
的測(cè)量也就結(jié)束了胁艰,而setMeasuredDimension
必須在onMeasure
當(dāng)中調(diào)用款筑,否則會(huì)拋出異常,所以我們觀察所有繼承于ViewGroup/View
的控件腾么,都會(huì)發(fā)現(xiàn)它們最后都是調(diào)用上面說(shuō)的那個(gè)方法奈梳。
前面我們已經(jīng)分析過(guò),measure
只是傳遞的紐帶解虱,因此它的邏輯是固定的攘须,我們直接看各個(gè)類的onMeasure
方法就好。
3.1 View
的onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
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;
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
這里殴泰,我們會(huì)根據(jù)前面所說(shuō)的于宙,父容器傳遞進(jìn)來(lái)measureSpec
中的mode
來(lái)給這兩個(gè)變量賦值:
- 如果
mode
為UNSPECIFIED
浮驳,那么說(shuō)明父容器并不指望多個(gè),因此子View
根據(jù)自己的背景或者minHeight/minWidth
屬性來(lái)給自己賦值捞魁。 - 如果是
AT_MOST
或者EXACTLY
至会,那么就把它設(shè)置為父容器指定的size
。
3.2 ViewGroup
的onMeasure
由于ViewGroup
的目的是為了容納各子View
谱俭,但是它并不確定子View
應(yīng)當(dāng)如何排列奉件,也就不知道該如何測(cè)量自己,因此它的onMeasure
是沒(méi)有任何意義的旺上,所以并沒(méi)有重寫瓶蚂,而是應(yīng)當(dāng)由繼承于它的控件來(lái)重寫該方法糖埋。
3.3 繼承于ViewGroup
控件的onMeasure
為了方面宣吱,我們以DecorView
為例,經(jīng)過(guò)前面的分析瞳别,我們知道當(dāng)我們?cè)?code>performTraversals中調(diào)用它的measure
方法時(shí)征候,最終會(huì)回調(diào)到它對(duì)應(yīng)的控件類型,也就是FrameLayout
的onMeasure
方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
我們可以看到祟敛,整個(gè)的onMeasure
其實(shí)分為三步:
- 遍歷所有子
View
疤坝,調(diào)用measureChildWithMargins
進(jìn)行第一次子View
的測(cè)量,在第一節(jié)中馆铁,我們也分析了這個(gè)方法跑揉,它最終也是調(diào)用子View
的measure
方法。 - 根據(jù)第一步的結(jié)果埠巨,調(diào)用
setMeasuredDimension
來(lái)設(shè)置自己的測(cè)量結(jié)果历谍。 - 遍歷所有子
View
,根據(jù)第二步的結(jié)果辣垒,調(diào)用child.measure
進(jìn)行第二次的測(cè)量望侈。
這也驗(yàn)證了第二節(jié)中的結(jié)論:父容器和子View
的關(guān)聯(lián)是通過(guò)measure
進(jìn)行關(guān)聯(lián)的。
同時(shí)我們也可以有一個(gè)新的結(jié)論勋桶,對(duì)于View
樹(shù)的某個(gè)節(jié)點(diǎn)脱衙,它的測(cè)量結(jié)果有可能并不是一次決定的,這是由于父容器可能需要依賴于子View
的測(cè)量結(jié)果例驹,而父容器的結(jié)果又可能會(huì)影響子View
捐韩,但是,我們需要保證這個(gè)過(guò)程不是無(wú)限調(diào)用的鹃锈。