这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战
MeasureSpec
MeasureSpec是一个类,由MODE_MASK和MODE_SHIFT组成一个32位int值,前高2位代码SpecMode,后面30位代表着SpecSize。SpecMode表示的是测量模式;SpecSize表示在某种测量模式下规格大小。
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
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(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
@UnsupportedAppUsage
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
}
SpecMode
通过源码可以知道SpecMode三种模式,分别是UNSPECIFIED、EXACTLY、AT_MOST。三种模式都是通过位运算得到。
- UNSPECIFIED UNSPECIFIED表示父容器不对View有任何限制,View可以自行设置多少尺寸大小。不需要开发者去关心,由系统内部调用。例如ScrollView视图。
- EXACTLY EXACTLY表示父容器已经测量出View所需大小。即MeasureSpec中SpecSize对应LayoutParams的match_parent和具体设定的数值。
- AT_MOST AT_MOST表示父容器置顶一个可用大小SpecSize。View的大小尺寸在这个值范围之内。即MeasureSpec中SpecSize对应LayoutParams的wrap_content。
LayoutParams和MeasureSpec关系
系统通过MeasureSpec来对View做测量外还能通过设置LayoutParams让View在父容器的约束下转换成对应MeasureSpec,然后再根据MeasureSpec测量View实际宽高。可以理解为MeasureSpec是由LayoutParams和父容器一起决定的。
DecorView的MeasureSpec计算会和普通View稍有不同,DecorView的MeasureSpec是根据屏幕尺寸和LayoutParams决定,对于DecorView的父容器可以说是屏幕了,它默认的LayoutParams是match_parent。对于普通View来说,MeasureSpec就是由父容器的MeasureSpec和自身LayoutParams所决定。
MeasureSpec创建
LayoutParams影响
在ViewRootImpl中在measureHierarchy方法中会执行getRootMeasureSpec,这是在计算View的MeasureSpec。通过getRootMeasureSpec就能清晰的知道MeasureSpec创建逻辑。
###ViewRootImpl.measureHierarchy
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
......
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
......
}
###ViewRootImpl.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:
// 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;
}
在getRootMeasureSpec方法中根据View的LayoutParams属性创建不同的MeasureSpec。
- LayoutParams.MATCH_PARENT 精确模式,尺寸大小是父容器窗口大小。
- LayoutParams.WRAP_CONTENT 尺寸大小不确定但最大不超过父容器窗口大小。
- default 默认没有设置则是精准模式大小由设定大小所决定。
父级容器ViewGroup影响
在ViewGroup当中有计算子View尺寸的方法measureChildWithMargins。其中在调用到child.measure()之前先通过方法getChildMeasureSpec计算得到子View的MeasureSpec。由此可知父级容器是会影响子View的尺寸大小计算的。
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);
}
ViewGroup的getChildMeasureSpec方法,通过先获取父容器SpecMode在通过子View的LayoutParams来计算出子View的SpecMode,最后是通过MeasureSpec.makeMeasureSpec(resultSize, resultMode)方法得到子View的MeasureSpec。
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:
......// 同上
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
......// 同上
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
| 子View Size/ 父级SpecMode | EXACTLY | AT_MOST | UNSPECIFIED |
|---|---|---|---|
| 具体尺寸设置 | EXACTLY | EXACTLY | EXACTLY |
| match_parent | EXACTLY | AT_MOST | UNSPECIFIED |
| wrap_content | AT_MOST | AT_MOST | UNSPECIFIED |
- 子View是具体尺寸设置时,父级SpecMode不管是什么,子View的SpecMode都是EXACTLY。
- 子View尺寸是match_parent,子View的SpecMode就是父级的SpecMode。尺寸大小是父容器剩余尺寸最大不超出父容器。
- 子View尺寸是wrap_content,子View的SpecMode都是AT_MOST。只有当父级SpecMode是UNSPECIFIED,子View设置了LayoutParams时才是UNSPECIFIED。