自定义控件——初识自定义控件

这篇博客介绍了Android自定义控件的基础知识,包括控件分类、自定义控件的分类,特别是自制控件的实现原理。详细讲解了onMeasure、onLayout和onDraw等关键方法,以及MeasureSpec的使用。文中通过实例展示了如何创建一个简单的自定义View和ViewGroup,强调了构造方法、测量、布局和绘制的重要性。

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

版权声明:本文出自 双门M 的专栏,转载或使用请注明出处。

开发的时候,因为业务需求或者封装需要,我们会进行自定义控件。

说在前面,本篇涉及到一些东西
* onMeasure
* onLayout
* onDraw
* MeasureSpec (32位二进制数,头两位模式(Mode),后两位大小(Size))
* onFinishInflate
* ViewGroup的getViewAt 方法
* MeasureSpec的makeMeasureSpec方法,getSize,getMode,和AT_MOST、EXACTLY、UNSPECIFIFD三个常量。
* getMeasuredWidth和getMeasuredHeight

一、控件的简单分类

简单粗暴来说,控件可以分为2种,View和ViewGroup

View 单独的控件,里面不能存放控件,继承自View。比如Imageview Button TextView。

TextView继承自View.png

ViewGroup 能存放控件的容器,比如FrameLayout、RelativeLayout 和LinearLayout等。

FrameLayout继承自ViewGroup.png

二、自定义控件的分类

自定义控件,类型各有各的分法,本人的对其进行大概如下分类:
* 自制控件 : 该自定义控件继承自View或者ViewGroup,自己绘制。
* 组合控件 : 利用系统已提供的控件,组合成一个新的控件
* 拓展控件 : 继承自系统已提供的控件并且加上新的功能或者特性。

既然如此,我们就开始来吧,从自制控件说起。
(本文的最后会附上关于三类自定义控件的博文链接)

三、自制控件

如果用最简单接地气的语言来描述自制控件,那么就是

1、继承自View或者ViewGroup
2、利用 onMeasure(测量)、onLayout(摆放)和onDraw(绘制) 三大步骤弄来弄去把View搞出来。(三者不必同时用到)
3、在利用事件的触发机制等调一调onTouchEvent等方法监听一下,然后利用Scroller或者ViewDragHelper等做做动画之类,合适怎么整就怎么整。
4、写写接口,调调回调,该谁干活谁就干活。

说成一句话:继承自View或者ViewGroup然后测量摆放绘制,然后做做动画,最后写写接口给调用者调用。

小二,来一个最简单的自制控件吧

1、 需要先认识的简单知识

提示:如果觉得琐碎可以直接从三.2的实例看起,最后再回过头来看这个三.1的内容。效果可能更佳。

我们的ViewGroup继承自View,在View里面有这么几个重要方法

首先三个我们已经提到的

1.1、measure和onMeasure(),layout()和onLayout(),draw()和onDraw()

  • measure() View的测量的入口,辗转调用真正工作的onMeasure()
  • layout() View的摆放的入口,辗转调用真正工作的onLayout()
  • draw() View的绘制的入口,辗转调用真正工作的onDraw()

其中,measure确定View的宽高,layout确定View的四个顶点的位置,而draw将View绘制在屏幕上。
measure()方法是final类型的方法,子类无法复写,而layout()和draw()子类就可以复写

  • onMeasure() 回调该方法对控件进行测量
  • onLayout() 回调该方法对控件位置进行摆放
  • onDraw() 回调该方法对控件进行绘制

measure和onMeasure()

关于measure(),需要分两种情况讨论,
情况1,只是View,那么通过View的measure() 可以完成对View的测量,而measure() 会去调用 onMeasure方法。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(
      getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
      getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

情况2,是ViewGroup,measure()除了完成对容器自身的测量之外,接着还会遍历去调用子元素(子元素可以使View或者ViewGroup)的measure(),各个子元素再去递归调用自身子元素的measure。注意是递归,递归,递归,重要的说三遍。
在ViewGroup的measure()会去调用onMeasure(),然后我们需要在onMeasure()里面对容器里的孩子进行 孩子.measure() 对孩子进行测量。

layout()和onLayout()
对于layout
如果是View,View调用layout可以指定View的位置
如果是ViewGroup,那么onLayout() 里面进行 孩子.layout 可以精确摆放孩子的位置

draw()和onDraw()

对于draw

这个就 比较简单了,他是按照如下几个流程走的
1、绘制背景 backgroud.draw(canvas)
2、绘制本身( onDraw )
3、绘制孩子( dispatchDraw )
4、绘制装饰 ( onDrawScrollBars )

其实实际开发中我们常用的,关心的也就是onDraw方法。

其实说了这么多,大概就是需要明白的是:

一个View的从无到有需要经过measure(),layout(),draw()三个步骤,measure()会辗转调用measure(),layout()会辗转调用onLayout,draw会转转调用onDraw().

如果是View,那么就写写onDraw,onLayout,至于onMeasure一般不需要。
如果是ViewGroup,那么些在onMeasure里面测量自身后接着进行对孩子的测量,在onLayout里面进行孩子的位置摆放。

.
.

1.2、onFinishInflate方法

我们知道,我们在Xml写的布局文件最终会在通过Pull解析的方式转成代码的。

onFinishInflate的作用,就是在xml加载组件完成后调用的。这个方法一般在自制ViewGroup的时候调用。

    /**
     * Finalize inflating a view from XML.  This is called as the last phase
     * of inflation, after all child views have been added.
     *
     * <p>Even if the subclass overrides onFinishInflate, they should always be
     * sure to call the super method, so that we get called.
     */
    @CallSuper
    protected void onFinishInflate() {
    }

.
.

1.3、ViewGroup的 getChildAt(int position) 方法

Returns the view at the specified position in the group.
返回该组中指定位置的视图。

这个位置按照我们include的顺序排列,索引从0开始。

    /**
     * Returns the view at the specified position in the group.
     *
     * @param index the position at which to get the view from
     * @return the view at the specified position or null if the position
     *         does not exist within the group
     */
    public View getChildAt(int index) {
        if (index < 0 || index >= mChildrenCount) {
            return null;
        }
        return mChildren[index];
    }

.
.

1.4 onMeasure和measure的参数

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        孩子.measure(32位的MeasureSpec宽,32位的MeasureSpec高);

    }

但看起来他们是一个int值,这两个参数可以是宽高的意思,但是不完全是,这宽和高都是32由 32位的二进制码组成的,并不是随随便便穿进去一个int类型的值就完事了的。

既然不能随随便便,那我们应该传什么?——我们应该传一个32位的二进制数。
那么我们计算出或者拿到这个数?—— 使用View类里面的MeasureSpec这个静态内部类。

1.5、 View类里面的静态内部类 MeasureSpec

我们首先需要看一个类,MeasureSpec

MeasureSpec.png

从上图我们可以看到,MeasureSpec这个类给我们提供了4个方法和3个常量

makeMeasureSpec和32位的二进制int值

先看看几个方法
* makeMeasureSpec(int size,int mode)
返回值是int,我们就是用MeasureSpec的makeMeasureSpec方法制造32位的二进制数给measure(宽,高)这个方法。

其中,size占30位,mode占2位

这个方法的2个参数
size(大小)怎么指定?
mode怎么确定?

* 利用LayoutParams得到size *

我们的View有多大谁知道?——我们在写xml的布局文件的时候就知道了有多大了。
但是怎么获取文件的在xml布局时的大小呢?
利用View的getLayoutParams()方法可以得到一个LayoutParams类型的值,利用
layoutParams.width 和
layoutParams.height
就可以获得View在布局时的宽高。

* 怎么确定mode *

mode有3种模式,分别是EXACTLY:精确模式;AT_MOST最大值模式,UNSPECIFIED : 未指定的模式,

MeasureSpec.EXACTLY,精确模式
当我们的 layout_width 和 layout_height 设定为
填充父窗体 match_parent 或者
指定具体数值 比如 android:layout_width=”130dp” 时
就可以采用EXACTLY这种模式

.

MeasureSpec.AT_MOST,最大值模式
当我们的 layout_width 和 layout_height 设定为 warp_content 时,控件大小随着内容大小的变化而变化,就是采用AT_MOST这种模式。

.

自定义控件Android开发中常见的任务之一。下面是一步一步教你如何自定义控件的简要指南: 第一步:创建一个新的Java类作为你的自定义控件。 首先,创建一个新的Java类,可以命名为你想要的控件名称。这个类应该继承自Android框架中的现有控件,例如View、TextView等。例如,如果你想要创建一个自定义按钮,可以创建一个名为CustomButton的类,并让它继承自Button类。 第二步:实现构造函数和属性。 在你的自定义控件类中,你可以实现构造函数和属性,以便对控件进行初始化和设置。你可以定义自己的属性,例如颜色、大小等,以及相应的getter和setter方法。 第三步:重写绘制方法。 要自定义控件的外观,你需要重写它的绘制方法。最常用的方法是重写`onDraw()`方法,在其中使用Canvas绘制你想要的形状、文本等。 第四步:处理用户交互。 如果你的自定义控件需要与用户进行交互,你可以重写相应的触摸事件(例如`onTouchEvent()`)或点击事件(例如`setOnClickListener()`)来处理用户操作。 第五步:在布局文件中使用自定义控件。 完成以上步骤后,你可以在布局文件中使用你的自定义控件了。只需在布局文件中添加一个与你的控件类名相对应的XML标签,并设置相应的属性。 这只是一个简要的指南,帮助你开始自定义控件的过程。在实际开发中,你可能需要更多的步骤和细节来完成你的自定义控件。你可以参考Android官方文档或其他教程来获取更多信息和示例代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值