MeasureSpec参与了View的measure过程,它在很大程度上决定了View的尺寸规格。(因为父容器影响View的MeasureSpec的创建过程)在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,根据这个MeasureSpec
测量
出View的宽/高(不一定等于最终宽高)
注意:MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要和父容器一起才能决定View的MeasureSpec
MeasureSpec
MeasureSpec代表一个32位int值(其本身是View的一个静态内部类),高2位代表测量模式SpecMode
,低30位代表在测量模式下的规格大小SpecSize
。把它俩打包成一个int值来避免过多的对象内存分配,MeasureSpec提供了打包和解包方法
SpecMode
三类
- UNSPECIFIED
父容器不对View有任何限制,要多大给多大(这种情况一般用于系统内部)表示一种测量的状态 - EXACTLY
父容器已经检测出View所需要的精确大小,这时View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式 - AT_MOST
父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,它对应于LayoutParams中的wrap_content
MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高
DecorView的MeasureSpec的创建过程
MeasureSpec的决定因素:
- 窗口的尺寸
- 自身的LayoutParams
ViewRootImpl # performTraversals() # measureHierarchy()
// desiredWindowWidth为窗口的宽度,lp.width为DecorView的layourParams中设置的宽度
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
// 得到DecorView的MeasureSpec宽高后测量,其中直接调用View的measure方法
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 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);
// }
// }
ViewRootImpl # getRootMeasureSpec()
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
// match_parent和fill_parent
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
// public static int makeMeasureSpec(int size, int mode) {
// if (sUseBrokenMakeMeasureSpec) {
// return size + mode;
// } else {
// return (size & ~MODE_MASK) | (mode & MODE_MASK);
// }
//}
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;
}
遵守如下规则,根据LayoutParams中的宽高参数来划分:
- LayoutParams.MATCH_PARENT:
SpecMode:EXACTLY SpecSize:windowSize - LayoutParams.WRAP_CONTENT:
SpecMode:AT_MOST SpecSize:windowSize(大小不定,不能超过窗口大小) - 固定大小:
SpecMode:EXACTLY SpecSize:rootDimension
普通View的MeasureSpec的创建过程
MeasureSpec的决定因素:
- 父容器的MeasureSpec
- 自身的LayoutParams
普通View的measure过程由ViewGroup传递过来
ViewGroup # measureChildWithMargins()
除了测量额外计算margin值外与measureChild基本一致
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
// parent...MeasureSpec是ViewGroup的MeasureSpec
// widthUsed是已经占用的宽度
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 第二个参数为ViewGroup中不属于自己的大小
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);
// 开始View的测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
ViewGroup # getChildMeasureSpec()
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 父布局的Mode和Size
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// 父布局原大小减去已用大小如果小于0,就取0
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
// EXACTLY
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
// AT_MOST
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
// UNSPECIFIED
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
// 如果设置了sUseZeroUnspecifiedMeasureSpec,大小就是0
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
创建规则
childLayoutParams\parent SpecMode | EXACTLY | AT_MOST | UNSPECIFIED |
---|---|---|---|
具体数值 | childSize EXACTLY | childSize EXACTLY | childSize EXACTLY |
match_parent | size EXACTLY | size AT_MOST | 0 or size UNSPECIFIED |
wrap_content | size AT_MOST | size AT_MOST | 0 or size UNSPECIFIED |