MeasureSpec类理解

MeasureSpec 常用在自定义View中,自定义View中经常要复写onMeasure方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

onMeasure() 是测量流程中的一个重要方法,只有完成测量才能完成接下来的 layout(布局) 和 Draw(绘图) 流程。

MeasureSpec 作用

MeasureSpec在很大程度上决定了一个View的尺寸规格,之所以说是很大程度上是因为这个过程还受父容器的影响,因为父容器影响View的MeasureSpec的创建过程。在测量过程中,系统会将View的LayoutParams根据父容器所施加规则转换成对应的MeasureSpec,然后在根据measureSpec来测量出View的宽/高。

MeasureSpec是View的一个内部类,常见三个方法:
  • makeMeasureSpec(int size ,int mode)
  • getMode(int measureSpec)
  • getSize(int measureSpec)

其中makeMeasureSpec()方法的作用将size 和 mode 打包成一个32位的int值,之所以这样做就是为了减少内存的分配。返回值为打包成的int类型值measureSpec 。 getMode 和 getSize 则是根据传入的int 类型值,解包成为 mode 和 size。

源码:

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 makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }

        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

省去不必要的代码,getMode方法中ModeMask 为 0x3 << 30 转换成二进制为 0011 << 30 ,也就是向左移动30位 则 ModeMask高两位为1,低三十位为0,整形measureSpec 为32位, measureSpec & mode_mask 就是高2位的运算,getSize方法中 ~Mode_MASK 则是除了高两位为0 外剩下的低30位均为 1,那么和measure 进行 & 运算就是在求得低30为中存储的值。

Mode 常见三种形式
  • UNSPECIFIED (未使用到过)
  • EXACTLY (包括精确dp 数据 和 LayoutParams.match_parent)
  • AT_MOST (LayoutParams.wrap_content)
两种测量形式:
  • activity 中最顶层View 是DecorView 的测量
  • 普通View measureSpec的测量

DecorView作为activity 中的顶层布局,要进行测量流程首当其冲。对于DecorView 其MeasureSpec由窗口的尺寸和其自身的LayoutParams决定的。

DecorView 涉及到的源码如下:
 childWidthMeasureSpec = getRootMeasureSpec(desireWindowWidth,lp.width);  
 childHeightMeasureSpec = getRootMeasureSpec(desireWindowHeight,lp.height);  
 performMeasure(childWidthMeasureSpec,childHeightMeasureSpec); 

其中desiredWindowWidth 和 desireWindowHeight 是屏幕的尺寸。

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;  
                   //Windows can resize.set max size for root view.  
                    measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.WRAP_CONTENT);  
                    break;  
                    default:  
                    //Window wants to be an exact size.Force root view to be that size.  
                    measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY);  
          }  
     } 

普通ViewMeasureSpec的生成

在ViewGroup 中存在两个方法:measureChildWithMargins() 和 measureChild()这两个方法,都是去生成子View的MeasureSpec的。下面给出measureChild方法源码:

 protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

上述方法调用了View的measure()方法,再对measure之前调用了getChildMeasureSpec()这个方法,从传递的参数上看,确定子元素的MeasureSpec需要父容器的MeasureSpec,以及margin padding 相关,还有子元素的LayoutParams. 具体看一下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);
    }

看// 表示的注释,可以很清楚的明白怎么回事。在这里我们还是只需要看case 为 AT_MOST 和 EXCTLY 两种情况即可。getChildMeasureSpec清楚的阐释了View的MeasureSpec的创建规则。

为了看的更加清晰我们可以根据这个方法总结出这样一个表格

这里写代码片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值