十八 、 View 的工作原理(2)---理解 MeasureSpec

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 创建总结图表:

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值