【Android】View的onMeasure方法详解及源码分析(一)

深入剖析View测量模式原理,包括EXACTLY、AT_MOST、UNSPECIFIED三种模式,及MeasureSpec如何用32位二进制表示模式与大小。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自定义View的时候,我们不可避免的需要重写View的onMeasure方法。但是网上的教程一般只告诉我们两点

1、getMode()方法得到View的测量模式(从网上随便找的)

  • EXACTLY:当宽高值设置为具体值时使用,如100DIP、match_parent等,此时取出的size是精确的尺寸;
  • AT_MOST:当宽高值设置为wrap_content时使用,此时取出的size是控件最大可获得的空间;
  • UNSPECIFIED:当没有指定宽高值时使用(很少见)。

2、getSize()方法得到View的大小

详细的查一下这两个方法,又会查到

widthMeasureSpec和heightMeasureSpec用32位二进制表示,高2位表示测量模式mode,低30位表示测量大小。

我以前也没有过于在意这些,一般自定义View的时候,按照这些,模式为EXACTLY时按照设置了具体值操作,一般设置为用户给定的大小。模式为AT_MOST时给一个默认的大小。UNSPECIFIED一般就按AT_MOST处理。就这样一直也没出过什么问题。

但是直到有一天,我终于遇到了UNSPECIFIED的情况。

//XML文件
<ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        
        <com.jarvislau.viewmeasuredemo.CustomView
            android:layout_width="match_parent"
            android:layout_height="1000dp" />
            
</ScrollView>
//CustomView.java
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        switch (mode) {
            case MeasureSpec.AT_MOST:
                Log.i("TEST","AT_MOST");
                break;
            case MeasureSpec.EXACTLY:
                Log.i("TEST","EXACTLY");
                break;
            case MeasureSpec.UNSPECIFIED:
                Log.i("TEST","UNSPECIFIED");
                break;
        }
}

结果输出了

TEST: UNSPECIFIED

第一次遇到这个情况我真是一脸懵逼,又爱又恨。恨的是写好的代码出问题了,爱的是终于遇到了一直不理解且“很少见”的UNSPECIFIED。

借着这个机会,我决定从源码入手,好好的分析一下这个UNSPECIFIED是怎么出现的,widthMeasureSpec和heightMeasureSpec是从哪来的,并且getMode和getSize到底是怎么用32位二进制表示的。

先说一下,第一次看源码的时候肯定会很慌,感觉什么都看不懂,其实只是因为系统定义的变量和方法太多了,各种命名自己不习惯,所以感觉都是不懂的代码,其实基本的android源码也都是java语言写的。只要静下心来一点点看,还是很容易看懂的。我们要学会不求甚解,有些不是我们重点关注的东西,看到命名知道大概是干什么的即可。

这篇文章我准备分几次写,第一篇先分析一下getMode和getSize是怎么用32位二进制表示的。之后我们再分析一下widthMeasureSpec和heightMeasureSpec是从哪来的。最后我们再总结一下三种测量模式的含义。

1、二进制及基本逻辑运算

View测量的getMode()和getSize()都用了二进制的计算。所以想知道这里是怎么表示mode和size的就必须得知道逻辑运算符。这里我们需要抛弃十进制的数,用二进制来计算和考虑。

我们ctrl进入MeasureSpec.getMode()和MeasureSpec.getSize()方法中看一下它的源码。里面很简单,就只有一行代码。

它们分别用到了“&”运算,“~”运算和“<<”运算

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

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

public static int getSize(int measureSpec) {
	return (measureSpec & ~MODE_MASK);
}

先简单说一下这几个运算符。

  • “&”“与”运算:只有相同位的两个数都是1的情况下得到1,其他情况得到0。
    如:
1101
&
0110
=
0100
  • “~”“非”运算:0变成1,1变成0。
    如:
~0101
=
 1010
  • “<<”“左移”运算:将二进制数向高位移动给定位数,空位补“0”,如“<<2”就是将二进制数向高位移动两位。这个可以将二进制数想象成有一个小数点,将小数点向右移就相当于二进制数字左移了。
01001
<<3
=
01001000

/*这里我们就可以理解为
01001.00000
将小数点右移3位,就是
01001000.00
去掉多余的0和我们自己用于辅助的小数点就是
01001000
我自己认为这样比较好理解*/

左移的实质是其实就是将数字乘以2的n次方。为什么可以相当于乘以2的n次方呢,这个可以用10进制想一下。10进制左移n位就相当于乘以10的n次方,那二进制不就是乘以2的n次方了嘛。右移也是一个道理,就相当于除以2的n次方了。

我们只介绍一下我们需要用到的二进制运算符。但是为什么android的源码里用了这么多二进制运算,并且非要用一个32位的二进制数的不同位数表示两个参数呢。测试了一下,在性能上几百万次的大数字乘除都看不出影响。这个大家可以讨论一下。

知道了这几个运算符的用法。我们再回来看一下getMode和getSize这两个方法中出现的MODE_MASK。

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;

MODE_MASK的值为常量0x3<<30。“0x”是代表后面的数是16进制。16进制的3也是10进制的3。因为int类型的最大位数为32位,且首位为符号位。也就是最高位的1代表负数,0代表正数。所以换算成32位的2进制就是。

0x3 = 0000 0000 0000 0000 0000 0000 0000 0011

这里我们隐隐约约的看到,这个二进制有两位是不一样的。正好对应了两位表示Mode,30位表示Size,不过现在是低两位和高30位不一样。别着急,我们还没左移呢。上面的二进制左移30位之后变成了。

MODE_MASK = 1100 0000 0000 0000 0000 0000 0000 0000

得到了MODE_MASK的二进制之后,我们再看getMode()方法

public static int getMode(int measureSpec) {
	return (measureSpec & MODE_MASK);
}

代码中又把measureSpec和MODE_MASK进行了“&”运算。之前我们说过了,“&”运算,相同位数的两个数,只有都是1的时候才得到1,其余所有情况都得0。所以不难看出,MODE_MASK的低30位全是0,已经对结果没有作用了,因为不管measureSpec是多少结果都是0。只有第31位和32位的两个数对mode的值有影响。

再看一下UNSPECIFIED、EXACTLY、AT_MOST三个常量的值。分别为:

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;

转换成32位二进制,也就是:

UNSPECIFIED = 0000 0000 0000 0000 0000 0000 0000 0000;
EXACTLY     = 0100 0000 0000 0000 0000 0000 0000 0000;
AT_MOST     = 1000 0000 0000 0000 0000 0000 0000 0000;

以上,我们基本可以确认android是如何用32位二进制表示View的测量模式的。为了严谨,我们用系统自带的Integer.toBinaryString()方法打印一下widthMeasureSpec和heightMeasureSpec的二进制,看看是不是和我们预期的一样。

int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

Log.i("TEST", "widthMeasureSpec Binary:" + Integer.toBinaryString(widthMeasureSpec));
Log.i("TEST", "heightMeasureSpec Binary:" + Integer.toBinaryString(heightMeasureSpec));

switch (modeWidth) {
	case MeasureSpec.AT_MOST:
		Log.i("TEST", "modeWidth:AT_MOST");
                break;
	case MeasureSpec.EXACTLY:
                Log.i("TEST", "modeWidth:EXACTLY");
                break;
	case MeasureSpec.UNSPECIFIED:
                Log.i("TEST", "modeWidth:UNSPECIFIED");
                break;
}
switch (modeHeight) {
	case MeasureSpec.AT_MOST:
                Log.i("TEST", "modeHeight:AT_MOST");
                break;
	case MeasureSpec.EXACTLY:
                Log.i("TEST", "modeHeight:EXACTLY");
                break;
	case MeasureSpec.UNSPECIFIED:
                Log.i("TEST", "modeHeight:UNSPECIFIED");
                break;
}

得到结果

TEST: widthMeasureSpec Binary:1000000000000000000010000111000
TEST: heightMeasureSpec Binary:11001100000

TEST: modeWidth:EXACTLY         //0100 0000 0000 0000 0000 0000 0000 0000
TEST: modeHeight:UNSPECIFIED    //0000 0000 0000 0000 0000 0000 0000 0000

将打印出的二进制补足到32位

widthMeasureSpec Binary:  0100 0000 0000 0000 0000 0100 0011 1000
heightMeasureSpec Binary: 0000 0000 0000 0000 0000 0110 0110 0000

再和MODE_MASK做“&”运算。正好得到的就是EXACTLY和UNSPECIFIED的值。

getMode()方法弄明白后,getSize()也就不难理解了。

public static int getSize(int measureSpec) {
	return (measureSpec & ~MODE_MASK);
}

先将MODE_MASK进行“~”运算

~ 1100 0000 0000 0000 0000 0000 0000 0000
=
  0011 1111 1111 1111 1111 1111 1111 1111

再将measureSpec和~MODE_MASK进行“&”运算。也就是前两位可以忽略掉了,后30位就是View的getSize()方法得到的二进制值。

View表示测量模式和大小的方式就是这么简单,这篇文章就先说这么多,之后我会从源码的角度分析一下onMeasure()方法的两个参数widthMeasureSpec和heightMeasureSpec是从哪来的,总结一下ViewGroup是怎么限制子View大小的。这个虽然不难,但是一点一点找起源码来还是要费一番功夫的。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值