MeasureSpec 是什么:
它是 Android 源码中 View.java 中的一个静态内部类:
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 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);
}
}
从源码中可以看到它的内部有一个静态方法 makeMeasureSpec() ,通过它可以创建一个 MeasureSpec 值,返回值为 int 类型,所以实际上 MeasureSpec 代表一个 32 位的 int 值。通过 getMode() 和 getSize() 方法可以看到它被分为两部分,分别为高 2 位为 SpecMode,低 30 位代表 SpecSize。SpecMode 是指测量模式,SpecSize 是指在某种测量模式下的规格大小,可以分别通过 getMode() 和 getSize() 方法获取。通过上面 Android 源码提供的方法我们可以看到,通过 View 的 MeasureSpec,我们可以使用 getMode() 和 getSize() 分别获取到它的 SpecMode 和 SpecSize;通过View 的 SpecMode 和 SpecSize,我们可以使用 makeMeasureSpec() 方法获取到它的 MeasureSpec 。
注意,上面所提到的 MeasureSpec 是指 MeasureSpec 所代表的 int 值,而并非 MeasureSpec 本身,它本身是 View 类中的一个静态内部类。
上面说了 SpecMode 是指测量模式,在 Android 系统中,它定义了三种情况:
UNSPECIFIED:(未指定模式)父容器不对 View 有任何限制,要多大给多大,这种情况一般用于系统内部。
EXACTLY:( /ɪg'zæk(t)lɪ/ 精确模式)父容器已经检测出 View 所需要的精确大小,这个时候 View 的最终大小就是 SpecSize 所指定的值。它对应于 LayoutParams 中的 match_parent 和具体的数值这两种模式。
AT_MOST:(最大模式)父容器指定了一个可用大小即 SpecSize,View 的最终大小不能大于这个值,具体是什么值要看不同 View 的具体实现。它对应于 LayoutParams 中的 wrap_content。
MeasureSpec 的作用:
它在 View 的测量过程中起作用。在 View 的 measure 方法中,需要 View 的 MeasureSpec 作为参数传递进去。Android 系统会根据 View 的 LayoutParams 以及父容器的 MeasureSpec 转换成对应的该 View 的 MeasureSpec,然后再根据这个 MeasureSpec 的值执行 measure() 方法,最后测量出 View 的宽/高。所以在很大程度上它决定了一个 View 的尺寸规格,之所以说很大程度上,是因为子 View 的 MeasureSpec 是由父容器的 MeasureSpec 和它本身的 LayoutParams 共同决定的,而不仅仅是由子 View 的 LayoutParams 来决定的。
MeasureSpec 和 LayoutParams 的关系以及 MeasureSpec 转换过程:
在 View 测量的时候,系统会将 View 的 LayoutParams 在父容器的约束下转换成对应的 MeasureSpec ,然后再根据这个 MeasureSpec 来确定 View 测量后的宽/高。所以,MeasureSpec 并不仅仅是由 View 的 LayoutParams 来决定的,LayoutParams 需要和父容器一起才能决定 View 的 MeasureSpec,从而进一步决定 View 的宽/高。
这里需要注意,对于顶层 View,也就是 DecorView 和普通 View ,它们的 MeasureSpec 转换过程略有不同:
DecorView 的 MeasureSpec 转换过程:由窗口的尺寸和其自身的 LayoutParams 来共同决定的。
普通 View 的 MeasureSpec 转换过程:由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定的。
MeasureSpec 一旦确定后,onMeasure() 方法中就可以确定 View 的测量宽/高,下面通过源码来具体分析一下 MeasureSpec 的转换过程:
DecorView 的 MeasureSpec 转换过程:
1. 对于 DecorView 来说,在 ViewRootImpl 中 的 measureHierarchy() 方法中开始它的测量过程:
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
boolean goodMeasure = false;
...
if (!goodMeasure) {
// desiredWindowWidth 和 desiredWindowHeight 是窗口的尺寸,lp 类型为 LayoutParams。
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
...
return windowSizeMayChange;
}
下面看一下 getRootMeasureSpec() 方法内部转换过程:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
// 根据 DecorView 的 LayoutParams 中的宽/高参数进行分支
switch (rootDimension) {
// 当宽/高设置为 match_parent 时,SpecMode 为 EXACTLY 模式
case ViewGroup.LayoutParams.MATCH_PARENT:
// 精确模式,大小就是 windowSize 的大小,也就是窗口的大小。
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
// 当宽/高设置为 wrap_content 时,SpecMode 为 AT_MOST 模式
case ViewGroup.LayoutParams.WRAP_CONTENT:
// 最大模式,大小是窗口的大小。
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// 默认为精确模式,大小为 DecorView 的 LayoutParams 中的宽/高
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
普通 View 的 MeasureSpec 转换过程:
1. 对于普通 View 来说,这里的普通 View 指的是我们布局中的 View,它的 measure 过程是由它的父布局 (ViewGroup) 传递而来的,所以我们从 ViewGroup 中 measure 的传递之前开始分析,它开始往子 View 传递的方法是在 measureChildWithMargins() 中:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
// 获取子 View 的 LayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 这里就是具体获取 View 的 MeasureSpec 方法
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);
}
2. 可以看到,先通过 getChildMeasureSpec() 方法获取子 View 的 MeasureSpec,然后再根据子 View 的 MeasureSpec 执行子 View 的 measure 过程。所以接下来看看 getChildMeasureSpec():
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 通过父容器的 MeasureSpec 获取其 SpecMode 和 SpecSize
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
/*
* padding = 父容器的 mPaddingLeft + 父容器的 mPaddingRight + 子 View LayoutParams的 leftMargin + 子 View LayoutParams的 rightMargin + 父容器已经使用的宽度 widthUsed
* size = 父容器的总大小 - 父容器中已占用的空间大小
* */
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
// 根据父容器中 MeasureSpec 中的 SpecMode 进行分支
switch (specMode) {
// 当父容器 SpecMode 为精确模式时
case MeasureSpec.EXACTLY:
// 再根据子 View 的 LayoutParams 进行判断
if (childDimension >= 0) {
// 当子 View 的 LayoutParams 为精确大小时
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 当子 View 的 LayoutParams 为 match_parent 时
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 当子 View 的 LayoutParams 为 wrap_content 时
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 当父容器 SpecMode 为最大模式时
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// 当子 View 的 LayoutParams 为精确大小时
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 当子 View 的 LayoutParams 为 match_parent 时
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 当子 View 的 LayoutParams 为 wrap_content 时
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 当父容器 SpecMode 为 UNSPECIFIED 模式时
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// 当子 View 的 LayoutParams 为精确大小时
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 当子 View 的 LayoutParams 为 match_parent 时
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 当子 View 的 LayoutParams 为 wrap_content 时
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
// 返回子 View 的 MeasureSpec
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
综上,可以看到普通 View 的 MeasureSpec 创建过程需要父容器的 MeasureSpec 以及该 View 本身的 LayoutParams 来共同决定的。
普通 View 的 MeasureSpec 创建总结图表: