关于android的MeasureSpec的解读

本文详细解析了在自定义View时如何处理onMeasure()方法中的MeasureSpec,解释了MeasureSpec的size和mode概念,以及如何通过位运算进行打包和解包操作。讨论了API17之前的潜在问题及其修复,同时指出makeMeasureSpec()方法的优化可能性。
摘要由CSDN通过智能技术生成

我们自定义控件的时候,除了需要复写onDraw()方法实现我们view的视图外,有时候还需要复写onMeasure()来进行对自定义控件的大小进行精确的计算,以便自定义控件能更好的显示其内容。

onMeasure(int widthMeasureSpec, int heightMeasureSpec)中的参数,是父控件传进来的对子控件大小进行说明和限制的信息,包含了size(大小)和mode(模式)的两个信息。google工程师为了不另外开辟对象空间来存储这两个信息,于是android提供了MeasureSpec这个类来打包size(大小)和mode(模式)为int值,并提供了解包的方法供提取出size(大小)和mode(模式)这两个信息。


API17以及之前,这个类提供的打包方法非常简单,但是存在bug。之后的版本已经修复了这个问题。(看代码,你能发现这个bug么)

         / 
         * @param size the size of the measure specification
         * @param mode the mode of the measure specification
         * @return the measure specification based on size and mode
         */
        public static int makeMeasureSpec(int size, int mode) {
            return size + mode;
        }

很容易看出这方法的参数 ,如果对换位置并不影响返回的结果。并且如果传入的其中一个值溢出,会导致测量结果的不准确。RelativeLayout就受到这个bug的影响。之后的版本,修复后的代码如下:

public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

方法是做了兼容,在API17版本之前还继续使用原来的实现方法。sUseBrokenMakeMeasureSpec代表当前API是否在17以及之前。 下面我们来看看完整的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;

        //打包大小和模式,传入的mode必须是上面定义好的三种模式之一
        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);
        }

    }

MODE_MASK:
11000000000000000000000000000000

UNSPECIFIED:
00000000000000000000000000000000

EXACTLY:
01000000000000000000000000000000

AT_MOST:
10000000000000000000000000000000

~MODE_MASK:
00111111111111111111111111111111

这里我们假设一下,当size等于50,mode为AT_MOST的时候,我们进行计算一下,首先是size部分:size&~MODE_MASK :

00000000000000000000000100010010
&
00111111111111111111111111111111
=
00000000000000000000000100010010

实际上没有变对吧。同样的,来看看mode部分:mode & MODE_MASK :

10000000000000000000000000000000
&
11000000000000000000000000000000
=
10000000000000000000000000000000

运算结果和模式原来的一样,然后整个计算就是很巧妙的,模式数据在前面,大小数据在后面,(size & ~MODE_MASK) | (mode & MODE_MASK)

00000000000000000000000100010010
|
10000000000000000000000000000000
=
10000000000000000000000100010010

上面的运算结果就包含了模式和大小的数据,模式数据存在头部,大小则存在尾部,取出的时候 ,我们看看如何。先看mode的取出方法: measureSpec & MODE_MASK:

10000000000000000000000100010010
&
11000000000000000000000000000000
=
10000000000000000000000000000000

然后取出size的运算:measureSpec & ~MODE_MASK

10000000000000000000000100010010
&
00111111111111111111111111111111
=
00000000000000000000000100010010

就这样,非常简单的使用逻辑运算进行打包和解包。现在回到打包的时候,打包的时候size部分的运算和mode部分的运算其实都没有变化(真的没有变化么),也就是说,makeMeasureSpec(int size, int mode)方法实际上可以简单点(少做点运算):

public static int makeMeasureSpec(int size, int mode) {
			return size|mode;
//			return (size & ~MODE_MASK) | (mode & MODE_MASK);
		}
注释掉的一行是谷歌工程师写的,至于为么这么写,我想应该是为了方便看出和取出方法相对应吧,你觉得呢?哈哈,当然不是了,如果按照简单的做法和API17以及之前的做法是一样的。试想一下,假如传入的size是负值,最高位符号位为1,运算后会影响到之后的size和mode取值的正确性。大家可以自己运算一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值