Android自定义控件
Tuivan opened this issue · 0 comments
Android开发里自定义控件可以说是基础功了,尽管开源社区里有很多优秀的控件供我们使用,但毕竟不是所有我们想要的控件都能找到,这时就需要自己去自定义控件了。之前我也刚好在学习如何自定义空件,所以下面我就简单介绍一下如何自定义控件。
简单介绍
Android里所有控件都继承自View,而ViewGroup则是容纳这些组件的容器,其本身也是从View派生出来的,自定义View和自定义ViewGroup在实现上也有所不同。
自定义控件通常要实现以下三个构造函数。
- View(Context context)
- View(Context context, AttributeSet attrs)
- View(Context context, AttributeSet attrs, int defStyleAttr)
第一个构造函数:当不需要使用xml声明或者不需要使用inflate动态加载时候,实现此构造函数即可
第二个构造函数:当需要在xml中声明此控件,则需要实现此构造函数。并且在构造函数中把自定义的属性与控件的数据成员连接起来。如果你有自定义XML属性,那么这些属性将会存放在参数中的attrs里。
第三个构造函数:接受一个style资源
这三个构造函数如果没特殊要求直接调用父类的构造函数就好。
除了构造函数,自定义控件最重要的是重写onMeasure(),onLayout(),onDraw()方法,运行时系统调用这三个方法的顺序是onMeasure()->onLayout()->onDraw(),不过它们其实都并非直接调用,而是先调用Measure(),Layout(),Draw()这三个方法,然后再由这三个方法去调用相应的方法。下面就重点介绍onMeasure(),onLayout(),onDraw()这三个方法。
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
onMeasure()这个方法是用来测量该控件的高和宽的,注意这里仅仅是测量大小,真正确定控件大小实际是在父控件里的OnLayout()里确定的,后面会提到onLayout()如何确定子控件大小。onMeasure()这里传入两个参数,注意到这两个参数名字后面都带有MeasureSpec,这里就要介绍一下MeasureSpec了。
MeasureSpec其实是父控件对子控件的布局要求,它其实是一个int类型的数据,不过它包含了两部分的信息,一个是大小,一个是模式,我们也不必知道它如何保存这两部分信息的,MeasurSpec类已经封装好提取和合成这两部分信息的方法了。
- static int getMode(int measureSpec)
- static int getSize(int measureSpec)
- static int makeMeasureSpec(int size,int mode)
这里介绍一下MeasureSpec的三种模式:
- EXACTLY 表示父控件希望子控件是这个大小
- AT_MOST 表示父控件希望子控件最多是这个大小
- UNSPECIFIED 表示父控件不对子控件大小做任何约束
这里要注意的是MeasureSpec仅仅是父控件对子控件的期盼,它其实是不具备任何实际约束力的,比如尽管传过来的模式是EXACTLY,你也可以让你自定义的控件测量得出来的是另外一个大小,不过多数情况下还是最好根据父控件给的期望去测量得出控件的大小。
说了那么多,onMeasure()又是如何测量控件大小的呢?
其实onMeasure最终目的是调用setMeasuredDimension(int measureWidth, int measureHeight)方法来确定测量的宽和高,当调用完这个方法之后,该控件的getMeasureWidth()和getMeasureHeight()方法将会获得相应的值,而父控件则会根据这两个方法得到的值去确定该控件的大小(如果没有调用setMeasuredDimension(),那么getMeasureWidth()和getMeasureHeight()方法得到的值便会是0)。
View和ViewGroup的onMeasure()实现有所不同,View只要测量自身大小就行了,而ViewGroup除了要测量自身大小外,还要调用子控件的Measure(int widthMeasureSpec, int heightMeasureSpec)方法来测量子控件的大小,参数里的MeasureSpec可由makeMeasureSpec()方法制作出来,这里要注意的是调用的是子控件的Measure(),而不是直接调用onMeasure()方法。我们可以先测量子控件的大小,然而再根据子控件的大小去确定ViewGroup测量的大小。
我们实现onMeasure()时可以调用控件的getLayoutParams()来获得XML布局里该控件的一些基础属性(比如长和宽),然后根据这些属性和父控件对该控件的期望来确定该控件的测量大小。
onLayout(boolean changed, int left, int top, int right, int bottom)
onLayout()这个方法是用来确定子控件的布局位置,这里传入了5个参数,第一个参数代表的是该控件的位置和大小有没有发生变化,通常情况下我们可以不用管。后面4个参数是该控件在父控件中的相对布局,也就是说其实这时候该控件的大小已经确定下来的了(比如宽便是right - left)。
onLayout()实际上只是用来确定子控件的布局位置即大小,所以View的这个方法是空白的,只有ViewGroup才需要重写这个方法。这4个参数实际上是是两个点的坐标X,Y值,再由这两个点确定一个的矩形既是该控件的布局范围,因为Android里X正向是向右,Y正向是向下,所以4个参数所代表的含义便成了左,上,右,下分别距离父控件左边或上面的大小。
在onLayout()里ViewGroup需要分别调用子控件的Layout()方法,需要传入4个参数来确定子控件在该控件中的相对布局位置及大小,而这4个参数的确定通常都要根据子控件的MeasureHeight和MeasureWidth来确定的,即需要根据子控件在onMeasure()方法中测量的大小来确定,当然你也可以不管子控件测量的大小去确定子控件的布局位置及大小。同样要注意的是这里调用的是子控件的Layout()方法而不是onLayout()。
onDraw(Canvas canvas)
onDraw()这个方法就是用来绘制图形了,要注意的是onDraw()绘制的只是该控件的图形,而不需要管子控件的图形,子控件的Draw()方法在ViewGroup的Draw()方法里已经调用了,所以并不需要我们再调用一次,所以如果实现的ViewGroup没有自带的外观就不用重写这方法了。
由于关于这方法如何描绘图形的内容比较多,所以我就不在这里介绍了,有兴趣的可以看一下后面我介绍的网址,里面有比较好的讲解,这里我们只需要知道onDraw()方法传入的画布大小与该控件的布局大小有关就好了。
一些后话
关于自定义控件我了解的也不算深入,基本都是根据网上的一些博客自学的,上面的介绍算是我对之前在博客上学的知识的一些总结,有些细节我也没能总结出来,如果大家有兴趣的话可以到我下面介绍的博客去看一下。
- Android LayoutInflater原理分析,带你一步步深入了解View(一)
关于自定义控件我主要是从这个博客学习,关于这方面的有四篇文章,里面都可以找到,不过里面讲的有关自定义控件的部分不多,讲的多数是有关View制作流程的东西,不过知道了View制作流程后自定义控件也不是难题了。另外里面源码可能比较多,看起来可能吃力一点。 - Android自定义控件三部曲文章索引
这篇是关于onDraw()绘图方面的,大家有兴趣可以看一下。 - Android事件分发机制完全解析,带你从源码的角度彻底理解(全)
这篇是关于触屏事件处理的。对于自定义控件来说,触屏事件的处理也是重要的一环,上面这篇文章是我看过的关于这方面写得最好的文章了,里面文章篇幅比较长,需要用的时间比较久,不过看完之后我是受益匪浅的。
学习自定义控件,除了看博客自学外,大家还可以去开源社区找一些别人做好的控件源码来看一下,可以更好地理解,然后最好可以自己尝试做一下。