11月更文挑战|Android基础-理解MeasureSpec

这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战

MeasureSpec

MeasureSpec是一个类,由MODE_MASKMODE_SHIFT组成一个32位int值,前高2位代码SpecMode,后面30位代表着SpecSizeSpecMode表示的是测量模式;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做测量外还能通过设置LayoutParamsView在父容器的约束下转换成对应MeasureSpec,然后再根据MeasureSpec测量View实际宽高。可以理解为MeasureSpec是由LayoutParams和父容器一起决定的。

DecorViewMeasureSpec计算会和普通View稍有不同,DecorViewMeasureSpec是根据屏幕尺寸和LayoutParams决定,对于DecorView的父容器可以说是屏幕了,它默认的LayoutParamsmatch_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);
}
复制代码

ViewGroupgetChildMeasureSpec方法,通过先获取父容器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。

猜你喜欢

转载自juejin.im/post/7031500826958888991