1 概述
網(wǎng)上有許多非常好的文章都在介紹MeasureSpec的測量規(guī)則,但是沒有介紹MeasureSpec的作用和應(yīng)用場景。

MeasureSpec是一個int,他將SpecMode和SpecSize封裝到了一起。
那么實際上MeasureSpec他是一個對:值和模式的一個封裝。
在這里,size和mode是成對出現(xiàn)的,他們一起作用。
MeasureSpec可以翻譯成:測量說明書。
他由手機(jī)屏幕的Window開始,將測量說明書生成并往下傳遞給DecorView,DecorView再生成自己的測量說明書,往下傳遞,不斷遞歸,每個ViewGroup都根據(jù)父View的測量說明書和自己的尺寸,生成自己的測量說明書并遞歸下去。
什么是測量說明書?由編寫測量說明書的一方(父View或者window)編寫測量說明書,告訴客戶(子View)要按照該測量說明書中的標(biāo)準(zhǔn)和規(guī)范來進(jìn)行測量操作,從而實現(xiàn)父View對子View尺寸限制。
試問,一個子View如何知道他的父View給他預(yù)留了多少尺寸?
答:父View通過調(diào)用子View的measure()方法,將父View留給子View的尺寸傳遞給子View。
2 MeasureSpec使用場景
MeasureSpec的使用場景分為兩個:
child View接收到parent View為自己生成的MeasureSpec對象,在onMeasure(int widthMeasureSpec, int heightMeasureSpec)中提取出該對象中的數(shù)據(jù)并調(diào)用setMeasureDimension為自己設(shè)置measureWidth和measureHeight.
-
parent View,即ViewGroup,這里先說下ViewGroup的onMeasure()方法的重寫套路:
在收到自己的onMeasure()回調(diào)的時候:
①要先對自己所有的子View進(jìn)行測量,一般是遍歷所有子View并調(diào)用ViewGroup的measureChildWithMargins(),然后再調(diào)用child.getMeasureWidth方法取出測量值,然后要么累加所有子View的測量者(如LinearLayout),要么從中選出最大的那個(如FrameLayout)。
②根據(jù)業(yè)務(wù)邏輯和他的父View設(shè)置給自己的MeasureSpec,來對他自己調(diào)用setMeasureDimension。
這里的第②點(diǎn)就和1.是一個東西,所以我們說的是①。
即②中,在測量所有的子View的時候,父View將為每個子View生成他專屬的MeasureSpec對象。
那么我們分別來看看這兩種使用場景中是如何使用MeasureSpec的。
按照MeasureSpec先創(chuàng)建后使用的順序,我們先看他的創(chuàng)建,后看他在子View中的使用
3 MeasureSpec的創(chuàng)建
3.1 Window為DecorView創(chuàng)建MeasureSpec
從最頂部開始:
ViewRootImpl.java
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
}
//...
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
//...
return windowSizeMayChange;
這里的performMeasure方法里面,調(diào)用了DecorView的measure,至此MeasureSpec對象開始從ViewTreee頂部開始向下傳遞。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
這里看下傳遞給DecorView的MeasureSpec是如何生成的:
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.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
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;
}
這里的windowSize是從WindowMananger獲取到的Window的視圖的尺寸,如手機(jī)屏幕大小。
而rootDimension參數(shù)是分別是DecorView的寬和高。而這里就是MATCH_PARENT,具體的定義要看創(chuàng)建DecorView的源碼的地方,對應(yīng)的是PhoneWindow類的installDecor()方法里。
通過這個getRootMeasureSpec()方法我們可以看到,創(chuàng)建的size和mode的對應(yīng)關(guān)系為:
| size | mode |
|---|---|
| MATCH_PARENT | MeasureSpec.EXACTLY |
| WRAP_CONTENT | MeasureSpec.AT_MOST |
| 具體值 | MeasureSpec.EXACTLY |
這是window為DecorView創(chuàng)建MeasureSpec時,為后者創(chuàng)建的MeasureSpec的對應(yīng)的規(guī)則,舉一反三一下,ViewGroup為子View創(chuàng)建MeasureSpec的時候,也是用的這種規(guī)則來生成MeasureSpec對象。
3.2 ViewGroup為子View創(chuàng)建MeasureSpec
測量子View這件事都是發(fā)生在ViewGroup的onMeasure中,而ViewGroup是沒有重寫onMeasure的,這個規(guī)則他留給了他的子類去重寫,我們找到一個最簡單的子類:FrameLayout,并截取部分他的onMeasure中的代碼:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
//...
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
//遍歷子View
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//測量子View
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
//提取子View的measuredWidth和measuredHeight
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);
//...
}
//...
}
那么看他用來測量子View調(diào)用的ViewGroup的方法:measureChildWithMargins
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//獲取子View的LayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//創(chuàng)建子View的寬的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
//創(chuàng)建子View的高的MeasureSpec
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//將這里創(chuàng)建的MeasureSpec傳遞給子View,并讓子View進(jìn)行測量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
當(dāng)創(chuàng)建子View的寬的MeasureSpec的時候,調(diào)用了getChildMeasureSpec方法
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//用父View的尺寸,減去已經(jīng)使用了的尺寸(包括父view的padding,子View的marging和父View已經(jīng)使用了的尺寸)。
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:
//...
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
注意這里的參數(shù)padding,加上了父View的padding和子View的margin和父View已經(jīng)使用了的尺寸(如果是FrameLayout,就是0,如果是LinearLayout,則會累加,看ViewGroup的邏輯而定)。size變量則是父View的尺寸減去上述的padding,得出留給子View的剩余空間的大小。
那么這里給子View生成MeasureSpec的邏輯就是:
父View剩余大小+父View的MeasureSpec+子View的尺寸 --> 子View的MeasureSpec
圖形化表示為:

這里有兩個點(diǎn)要注意一下:
-
當(dāng)父View的布局大小確定的時候,即EXACTLY的時候,那么生成的子View的MeasureSpec依然符合上面創(chuàng)建根布局的情況:
size mode MATCH_PARENT MeasureSpec.EXACTLY WRAP_CONTENT MeasureSpec.AT_MOST 具體值 MeasureSpec.EXACTLY 而當(dāng)父View自己的布局大小都不確定的時候,即AT_MOST時(即父View也是用的WRAP_CONTENT,所以他才得到了AT_MOST的mode),子View的MATCH_PARENT其實就是要求和父View一樣的大,那父View不確定大小,子View自然也不確定大小了,即AT_MOST。
當(dāng)子View的尺寸是WRAP_CONTENT的時候,父View給子View生成的size是父View剩下的size。
為什么?因為父View在此時也不知道你子View有多大,那就把父View剩余的大小給子View,并告訴子View模式是AT_MOST,你子View最大不要超過我給你的這個大小,剩下的你盡管發(fā)揮。
4 MeasureSpec的使用
4.1 View的onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
這里是用的getDefaultSize()方法來獲取最終的子View的寬高,并用setMeasuredDimension()設(shè)置到自己的屬性(稍后父View就可以獲取到子View給自己測量的大小了)。
getDefaultSize的兩個參數(shù)一個是獲取建議的最小寬度,一個是父View給的測量說明書。
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
看下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給的測量模式是UNSPECIFIED,那就用getSuggestedMinimumWidth返回的大小。如果父View給的測量模式是AT_MOST或者EXACTLY,那就直接用父View給我們生成的大小。
這里要牽扯到一個自定義View的技巧:自定義View要重寫onMeasure()方法來處理AT_MOST的測量模式。
從前面創(chuàng)建MeasureSpec知道,當(dāng)子View用了wrap_content的時候,父View就會給你生成AT_MOST的測量模式,但因為AT_MOST測量模式下也是用的父View返回的尺寸,這時父View返回的尺寸是父View剩下的尺寸。他的意思是:這些尺寸給你,但是這是你能使用的最大的尺寸,不要超過這個尺寸就行。
一般來說我們會在自定義View中重寫并判斷AT_MOST時,返回一個默認(rèn)的值。
比如這樣:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//默認(rèn)尺寸,寫死或者根據(jù)業(yè)務(wù)邏輯計算得到
val defaultWidth = 50
val defaultHeight = 100
//取出父View給的測量模式
val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec)
val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec)
//取出父View給的測量尺寸
val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec)
val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec)
//最終測量尺寸
val finalWidth = if (widthSpecMode == MeasureSpec.AT_MOST) defaultWidth else widthSpecSize
val finalHeight = if (heightSpecMode == MeasureSpec.AT_MOST) defaultHeight else heightSpecSize
//set
setMeasuredDimension(finalWidth,finalHeight)
}
這種是手動計算的,還有一種是借助View自帶的resolve()方法來去計算的。
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//默認(rèn)尺寸
val defaultWidth = 50
val defaultHeight = 100
//最終測量尺寸(注意,也計算了padding)
val finalWidth = resolveSize(defaultWidth + paddingLeft + paddingRight, widthMeasureSpec)
val finalHeight = resolveSize(defaultHeight + paddingTop + paddingBottom, heightMeasureSpec)
//設(shè)置
setMeasuredDimension(finalWidth, finalHeight)
}
resolveSize()方法內(nèi)部有更精細(xì)的判斷:
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
View對MeasureSpec是使用方,非創(chuàng)建方。
因此我們可以直接按照MeasureSpec的字面意思來直接理解:
SpecSize就是你父View給我指定的測量的大小。那么我拿到了這個大小我要怎么用呢?看你給我生成的測量模式SpecMode,如果是EXACTLY,那父View的意思就是直接讓我用這個size作為最終我的測量大小就行了。如果是AT_MOST,父View傳遞給我的消息是,這個size不是讓你作為最終的size的,我只是把我剩下的尺寸給你了,你不要超過這個尺寸即可。
4.2 FrameLayout的onMeasure
實際上我們想看的是ViewGroup在onMeasure中是如何使用MeasureSpec的,但是ViewGroup沒有重寫,直接沿用改的View的,因此找了個簡單的FrameLayout的來看看。
@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;
//遍歷子View
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();
//取到所有子View中尺寸最大的那個尺寸
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);
}
}
}
}
// 計算FrameLayout自身的padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// 再次檢查 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());
}
//調(diào)用resolveSizeAndState()方法去獲取最終測量值
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);
}
}
}
ViewGroup在對自己測量的時候,也是調(diào)用的resolveSizeAndState這個方法來計算出最終的測量值的。
5 總結(jié)
對于MeasureSpec創(chuàng)建者ViewGroup, 根據(jù)子View的LayoutParams的width和heigth和自己的MeasureSpec來為子View創(chuàng)建出MeasureSpec。傳遞給子View,并讓子View根據(jù)該MeasureSpec設(shè)置對應(yīng)的測量寬高,然后父View再拿到子View測量寬高,將上述動作遍歷所有子View后,再對自己進(jìn)行測量,設(shè)置自己的寬高。
對于MeasureSpec使用者View,根據(jù)父View傳遞進(jìn)來個MeasureSpec,結(jié)合自身邏輯,計算出自己的寬高。