这是我参与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。