MeasureSpec 常用在自定义View中,自定义View中经常要复写onMeasure方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
onMeasure() 是测量流程中的一个重要方法,只有完成测量才能完成接下来的 layout(布局) 和 Draw(绘图) 流程。
MeasureSpec 作用
MeasureSpec在很大程度上决定了一个View的尺寸规格,之所以说是很大程度上是因为这个过程还受父容器的影响,因为父容器影响View的MeasureSpec的创建过程。在测量过程中,系统会将View的LayoutParams根据父容器所施加规则转换成对应的MeasureSpec,然后在根据measureSpec来测量出View的宽/高。
MeasureSpec是View的一个内部类,常见三个方法:
- makeMeasureSpec(int size ,int mode)
- getMode(int measureSpec)
- getSize(int measureSpec)
其中makeMeasureSpec()方法的作用将size 和 mode 打包成一个32位的int值,之所以这样做就是为了减少内存的分配。返回值为打包成的int类型值measureSpec 。 getMode 和 getSize 则是根据传入的int 类型值,解包成为 mode 和 size。
源码:
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 makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
省去不必要的代码,getMode方法中ModeMask 为 0x3 << 30 转换成二进制为 0011 << 30 ,也就是向左移动30位 则 ModeMask高两位为1,低三十位为0,整形measureSpec 为32位, measureSpec & mode_mask 就是高2位的运算,getSize方法中 ~Mode_MASK 则是除了高两位为0 外剩下的低30位均为 1,那么和measure 进行 & 运算就是在求得低30为中存储的值。
Mode 常见三种形式
- UNSPECIFIED (未使用到过)
- EXACTLY (包括精确dp 数据 和 LayoutParams.match_parent)
- AT_MOST (LayoutParams.wrap_content)
两种测量形式:
- activity 中最顶层View 是DecorView 的测量
- 普通View measureSpec的测量
DecorView作为activity 中的顶层布局,要进行测量流程首当其冲。对于DecorView 其MeasureSpec由窗口的尺寸和其自身的LayoutParams决定的。
DecorView 涉及到的源码如下:
childWidthMeasureSpec = getRootMeasureSpec(desireWindowWidth,lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desireWindowHeight,lp.height);
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
其中desiredWindowWidth 和 desireWindowHeight 是屏幕的尺寸。
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.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT;
//Windows can resize.set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.WRAP_CONTENT);
break;
default:
//Window wants to be an exact size.Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY);
}
}
普通ViewMeasureSpec的生成
在ViewGroup 中存在两个方法:measureChildWithMargins() 和 measureChild()这两个方法,都是去生成子View的MeasureSpec的。下面给出measureChild方法源码:
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
上述方法调用了View的measure()方法,再对measure之前调用了getChildMeasureSpec()这个方法,从传递的参数上看,确定子元素的MeasureSpec需要父容器的MeasureSpec,以及margin padding 相关,还有子元素的LayoutParams. 具体看一下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 = 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);
}
看// 表示的注释,可以很清楚的明白怎么回事。在这里我们还是只需要看case 为 AT_MOST 和 EXCTLY 两种情况即可。getChildMeasureSpec清楚的阐释了View的MeasureSpec的创建规则。
为了看的更加清晰我们可以根据这个方法总结出这样一个表格