深入理解MeasureSpec

前言

上一篇DecorView添加到Window过程的源码分析我们找到了UI绘制流程的起始点,也就是在ViewRootImplperformTraversals()依次执行performMeasureperformLayoutperformDraw,那么这个MeasureSpec又是什么呢?它是View的一个内部类,从名字我们可以看出,这是一个测量规格,它决定了View的测量过程。

MeasureSpec

    /**
     * A MeasureSpec encapsulates the layout requirements passed from parent to child.
     * Each MeasureSpec represents a requirement for either the width or the height.
     * A MeasureSpec is comprised of a size and a mode. There are three possible
    */
      * MeasureSpec封装从父对象传递给孩子的布局要求。
      * 每个MeasureSpec表示宽度或高度的要求。
      * MeasureSpec由尺寸和模式组成。 有三种模式:
    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)
            }
        }
}

从MeasureSpec类的定义我们知道,它封装了对子View的布局要求,由尺寸和模式组成,其实MeasureSpec代表一个32位的int值,高2位表示SpecMode,低30位表示SpecSize,而SpecSize是指在某种SpecMode下的规格大小,从源码我们看出它内部定义了很多常量,从api17以后开始采用位运算,因为位运算的效率最高,我们看下三种模式

  • UNSPECIFIED = 0 << MODE_SHIFT:即: 00000000 00000000 00000000 00000000父容器不对子View有任何限制,子View要多大给多大,有系统内部调用,我们不需要研究,例如ScrollView
  • EXACTLY =1<< MODE_SHIFT:即: 01000000 00000000 00000000 00000000父容器已经测量出子View所需要的大小,即measureSpec中封装的specsize,对应于LayoutParams中的match_parent和设置的固定值
  • AT_MOST =2 << MODE_SHIFT:即: 10000000 00000000 00000000 00000000父窗口限定了一个最大值给子View即SpecSize,对应于LayoutParams中的wrap_content
    size & ~MODE_MASK是获得SpecSize,mode & MODE_MASK是获得SpecMode,之后再或运算即可得到MeasureSpec。看下图具体运算
深入理解MeasureSpec_第1张图片
位运算与.png

深入理解MeasureSpec_第2张图片
位运算与非.png

我们在使用View时是直接设置LayoutParams,但是在View测量的时候,系统会将LayoutParams在父容器的约束下进行相对应的MeasureSpec,然后在根据这个MeasureSpec来确定View的测量后的宽高,由此可见,MeasureSpec不是LayoutParams唯一决定的,还需要父容器一起来决定,在进一步决定View的宽高。但是顶级View,也就是上文我们分析到的DecorView和普通的View的MeasureSpec计算有些区别,对于DecorView,其MeasureSpec是由屏幕的尺寸和LayoutParams决定的,而DecorView的默认LayoutParams就是match_parent(在初始化DecorView时可知),对于普通View来说,其MeasureSpec是由父容器的MeasureSpec和自身的LayoutParams决定。在performTraversals()方法中有如下一段

    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

我们再来看下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;
    }

通过上述代码,对于DecorView来说就是走第一个case,对于普通View来说,也就是我们Activity显示布局的根View是一个ViewGroup,我们再来看下ViewGroup的measureChildWithMargins()方法

ViewGroup.java#measureChildWithMargins()

    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);
    }

可以发现在调用子View的measure()之前会先通过getChildMeasureSpec()方法来得到子View的MeasureSpec,通过分析,我们明显可以发现子View的MeasureSpec的创建与父容器的MeasureSpec和自身的LayoutParams有关,我们再来看下getChildMeasureSpec()

ViewGroup.java#getChildMeasureSpec()

    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:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } 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 asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

上面代码主要是先获得到父容器的SpecMode,在通过子View自身设置的LayoutParams来进一步决定子View的SpecMode,最后在通过MeasureSpec.makeMeasureSpec(resultSize, resultMode);返回子View的MeasureSpec然后再去measure().getChildMeasureSpec()这个方法很清晰的展示出子View创建MeasureSpec的过程,下面我们通过表格在说明一下

深入理解MeasureSpec_第3张图片
普通View的MeasureSpec创建规则
  • View采用固定宽/高时(即设置固定的dp/px),不管父容器的MeasureSpec是什么,View的MeasureSpec都是EXACTLY模式,并且大小遵循我们设置的值。
  • View的宽/高是match_parents时,如果父容器的模式是精准模式,那么View也是精准模式并且其大小是父容器的剩余空间;如果父容器是最大模式那么View也是最大模式并且其大小不会超过父容器的剩余空间
  • View的宽/高是wrap_content时,View的MeasureSpec都是AT_MOST模式并且其大小不能超过父容器的剩余空间。
    只要提供父容器的MeasureSpec和子元素的LayoutParams,就可以确定出子元素的MeasureSpec,进一步便可以确定出测量后的大小。

总结

好了,至此为止,MeasureSpec含义模式以及创建规则就基本说完了,下一篇开始我们分析onMeasure()等调用过程和相关方法。

推荐

DecorView添加到Window过程的源码分析
AppCompatActivity的setContentView()源码分析
Activity的setContentView()源码分析


你可能感兴趣的:(深入理解MeasureSpec)