【Android进阶之自定义View(一)】

本文深入讲解了Android自定义View的基本原理,重点介绍了onMeasure()方法的作用及其实现方式,帮助读者理解View绘制流程。

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

自定义View

Android系统本身给我们提供了大量的控件,可以满足大部分的应用,但是往往在实际的项目过程中有很多需求是无法使用自带的控件来完成的,这个时候就得通过自定义View的方式来满足需求。

自定义View对于初学者来说会感觉比较困难,里面涉及到的知识点也很多,往往让初学者很茫然,我本人也是一个初学者,在刚开始接触的时候感觉很茫然,不知道怎么下手,哪怕是现在,也觉得很难,所以在这里写下文章以记录我学习自定义View的过程。

在自定义View时,我们通常会重写onDraw()方法来绘制View的显示内容,如果该View还需要使用wrap_content属性时还必须重写onMeasure()方法。


View中比较重要的回调方法

在View中通常会有以下一些比较重要的回调方法:

  • onFinishInflate() : 改方法通常在XML中加载组件后回调;
  • onSizeChanged() : 组件大小改变时回调;
  • onMeasure() : 回调该方法进行测量;
  • onLayout() : 回调该方法用来确定显示的位置;
  • onTouchEvent() : 监听到触摸事件时进行回调;

当然,在创建具体的View时你可能并不会重写所有的这些方法,在自定义View时最常见的重写的方法有onMeasure()和onTouchEvent()方法。


Android View的绘制过程之onMeasure

为了更好的进行自定义View,很有必要对View的绘制流程进行了解,这其中涉及到一些源码的阅读,通过阅读View绘制的源码,你会对View是怎么绘制到屏幕上的以及怎样进行自定义View有一个更深刻的理解。

任何一个视图都是要经过详细的绘制流程才能显示在屏幕上,每一个视图的绘制通过经历一下三个主要步骤,即onMeasure()、onLayout()和onDraw(),今天主要介绍一下onMeasure()方法的相关流程,相关内容参考了郭霖大神的博客: Android视图绘制流程完全解析,带你一步步深入了解View(二)

onMeasure()方法时用来对视图的大小进行测量的,View系统的绘制流程是从ViewRoot的performTraversals()方法中开始的,在该方法中会调用View的measure()方法,measure()方法有两个参数,分别为widthMeasureSpec和heightMeasureSpec,这两个参数由父View传递过来,用来确定视图的宽度和高度的规格和大小。measure()方法最终会调用View的onMeasure()方法确定View的大小。

MeasureSpec的值有specSize和specMode共同组成,其中specSize记录的是大小,而specMode记录的是规格,specMode有以下取值:

  • EXACTLY
    表示父视图希望子视图的大小应该是由specSize的大小来确定的,通常设置为具体的大小或者match_parent时会取此值。

  • AT_MOST
    表示子视图的大小最多只能是specSize的大小,通常设置为wrap_content时会取此值,此时一般需要重写onMeasure()方法。

  • UNSPECIFIED
    表示可以设置为任意的大小,没有任何的限制,使用较少。

前面提到过,父视图会调用子视图的measure()方法,并传入两个参数widthMeasureSpec和heightMeasureSpec,并且最终在measure()方法中会调用View的onMeasure()方法,下面我们先来看一下measure()方法中到底做了什么?

public final void measure(int widthMeasureSpec int heightMeasureSpec) {
    if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) {
        mPrivateFlags &= ~MEASURED_DIMESION_SET;
        if (ViewDebeg.TRACE_HIERARCHY) {
            ViewDebug.trace(this, ViewDebug.HierarchyTrace.ON_MEASURE);
        }
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        if((mPrivateFlags & MEASURED_DIMESION_SET) != MEASURED_DIMESION_SET) {
            throw new IllegalStateException("onMeasure() did not set the" + " measured dimesion by calling" + " setMeasuredDimesion()");
        }
        mPrivateFlags |= LAYOUT_REQUIRED;
    }
    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;
}

从上面的代码中相信你会注意到measure()方法前面的final 标志,表明该方法是不可重写的,同样的你会发现在measure()函数中我们调用了onMeasure()方法,这个方法允许我们进行重写,这里放置的就是我们真正去测量并设置View大小的地方,默认会调用getDefaultSize()方法来获取视图的大小,代码如下所示:

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    switch(specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
    }
    return result;
}

分析上面的代码你会发现,当specMode = AT_MOST | EXACTLY时,返回specSize,这是系统默认时行为,当specMode = UNSPECIFIED时,会返回设置的size值,之后会在onMeasure()方法中调用setMeasuredDimesion()方法来设定测量的大小。

当然如果你不想使用系统默认的方法,你可以不调用getDefaultSize()方法,而在onMeasure()方法中根据不同的specMode值调用不同的测量逻辑。

举个例子:

public class MyView extends View {
    ......
    @Override
    protected void onMeasure(int widthMeasureSpec, heightMeasureSpec) {
        setMeasuredDimesion(200, 200);
    }
}

这样自定义View带来的后果就是不管在布局文件中定义View的大小如何,最终在界面上显示的View的大小都为200 * 200。

这里写了一个最简单的自定义View的例子,仅仅修改了onMeasure()中的方法,使得无论在xml中定义的View的大小如何,始终将View的大小设为固定的200 * 200,同时在onDraw()方法中绘制了一个(0,0)到(getMeasuredWidth(), getMeasuredHeight())大小的矩形,当在xml文件中改变view的大小时,绘制出来的矩形都不会发生变化。链接如下:[UserDefinedView01]https://github.com/Glemontree/UserDefinedView01.


后话

其实很多次都有想写博客的冲动,也尝试过几次,但是每次都中途而废,一是因为自己是在自学android,平时只有晚上的时间可以学习一下,所以学习进度较慢;另外一方面是因为自己的知识储备不够,有时候想写东西又不知道写什么,很是纠结,自己也是看着别人的博客过来的,当写相关内容时,又难免会参考别人的博客,总觉得这样不好,木有原创的内容。不过后来觉得,现在网络资源那么丰富,参考别人的自己再加以总结很正常,可怕的不是你参考别人的文章,而是你在参考的过程中木有自己的思考。

写博客是很好的习惯,可以对自己学习到的进行总结,发现自己的不足和弱势,还可以将自己学习到的,遇到的坑分享给别人,很乐而不为呢?自己以后反过来看自己以前写的博客,肯定有很多感慨,从一篇篇的博客中可以看到自己的成长,那也是一种满足,所以一起写起来吧。

坚持,加油!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值