自定义View的时候onMeasure()理解

本文深入解析Android自定义View中onMeasure()方法的作用,详细解释LayoutParams和父容器规则对View尺寸的影响,并通过实例演示如何利用MeasureSpec确定View的测量值。

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

在自定义View的时候,我们第一步往往是进行测量,重写onMeaure()的方法,至于为什么要重写它和怎么重写它,我在刚开始学的时候都是按照书上和博客上或者视频上照着葫芦画瓢,而且书上(疯狂Android讲义)也说的不清不楚,导致我一直没能理解它。在后来开发过程中多次用到onMeaure()的方法后,才开始有点理解。下面我按照我的理解写一下笔记。

我们自定义View总是要显示在屏幕上的对吧?那么显示多大呢?这是由两个东西来共同决定:

  • 自定义View的LayoutParams
  • 父容器所施加的规则

什么是 — 自定义View的LayoutParams?

就是你在XML文件里面加上你自定义View的layout_widthlayout_heigh的参数,如下所示:

    <com.zjy.example.mView
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

我们用到的就三种参数对吧:

  • match_parent
  • wrap_content
  • 自己给定一个确切的值

什么是 — 父容器所施加的规则?

不管是父容器还是自定义View自己,都会有规则,这个规则有三个:

  • UNSPECIFIED:父容器不对自定义View有任何限制
  • EXACTLY:父容器已经检测出自定义View的大小,最后自定义View的大小就是这个大小
  • AT_MOST:父容器给出一个大小,自定义View不能够大于此值

自定义View到底显示多大呢?

有了这“自定义View的LayoutParams”和“父容器所施加的规则”两个东西,如何决定自定义View的在屏幕的大小?
我们来看看onMeaure()是怎么做的

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ···//一系列操作
        setMeasuredDimension(width, width);//最后设置宽高的测量值
    }

onMeaure() 的两个参数是名字里面都有MeasureSpec这一个单词,其实MeasureSpec是View的一个静态内部类,我们在View里面就可以看到它的源码(我把不重要的删掉了,只看看它的具体作用):

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 getMode(int measureSpec) {}

        public static int getSize(int measureSpec) {}
    }

从代码看出它其实代表一个32位的int值,高2位代表上面所介绍的3种规则,第30位代表大小。比如AT_MOST就是“10”,在MeasureSpec 里面呢就是“10”右移30位,这样就代表高32位中的高2位是代表规则,专业术语是“SpecMode”。低30位代表大小(在高2位的SpecMode规则下的大小),叫“SpecSize”。

所以onMeaure() 的两个参数widthMeasureSpec, heightMeasureSpec也包含了SpecModeSpecSize
widthMeasureSpec, heightMeasureSpec父容器对自定义View提出的水平和垂直的空间要求

系统内部将会对“自定义View的LayoutParams”在“父容器所施加的规则”下转换成MeasureSpec ,然后在根据MeasureSpec 来确定View测量后的宽高

刚才说了,MeasureSpec 是View的一个静态内部类,应为我们自定义View肯定是要继承View的,所以我们可以用MeasureSpec 的两个方法来获取SpecMode和SpecSize:

int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);

int highMode = MeasureSpec.getMode(heightMeasureSpec);
int highSize = MeasureSpec.getSize(heightMeasureSpec);

我们取得SpecMode和SpecSize之后,就可以按照我们自己的要求来设置自定义View的大小了,比如简单的我们想自定义View是以正方形显示的,取正方形的宽度为widthSize和highSize最小值,再调用setMeasuredDimension()函数进行设置自定义View的宽高的测量值就可以了,当然这一切步骤都是在onMeasure()函数里面完成的。

int width = Math.min(widthSize, highSize);
setMeasuredDimension(width, width);//最后设置宽高的测量值

异常情况

我们会好奇了,widthMode和highMode我们提取出来干嘛的呢?因为自定义View会出现一些异常情况,有时候“自定义View的LayoutParams”设置为“wrap_content”的时候,它在显示的时候却不是“wrap_content”的。这时候提取出widthMode和highMode就派上用处了。可以如下设置:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int highMode = MeasureSpec.getMode(heightMeasureSpec);
        int highSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode == MeasureSpec.AT_MOST && highMode == MeasureSpec.AT_MOST){
            //最后设置宽高的测量值,mWidth和mHeigth是我们自己给定的默认宽高
            setMeasuredDimension(mWidth, mHeigth);
        }else if (highMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSize , mHeigth);
        }else if (widthMode  == MeasureSpec.AT_MOST){
            setMeasuredDimension(mWidth, highSize);
        }

    }

为什么这么设置呢?建议去看看《Android开发艺术探索》的第4章~~~那是本好书~


参考:

Android开发艺术探索-任玉刚

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值