自定义 View 基本是每一名 Android 开发工程师都应该会的技能,其实搞清楚原理后,其实也没有那么困难,大多数童鞋都是直接被自定义 View 多还繁杂的流程所吓住。
对于自定义 View 和属性动画等操作,相信我无论如何也写不出扔物线的 https://hencoder.com/ 的高度,所以并不打算在这个上面浪费口舌。
不过,我们今天倒是可以来探讨一下,平时大家在自定义 View 的时候都有些什么需要我们去注意的。
对于自定义 View
对于大多数自定义 View 来说,如果是要绘制某些东西,直接在 onDraw()
方法中进行绘制即可,如果要处理触摸事件,那么直接重写 onTouchEvent()
并在里面编写自己的逻辑即可。大多数情况下,我们都需要重写 onMeasure()
,如果不重写的话,就必须在外面指定好宽高,相信不会有人希望看到 wrap_content
和 match_parent
一样的展示效果吧。
对于 View 的测量的话,我想每一个人都得清楚 MeasureSpec
这个类的 Mode 和 size。对于 onMeasure()
里面的测量比较重要的可以移步到我之前的一篇文章:每日一问:谈谈你对 MeasureSpec 的理解。文章中详细介绍了 MeasureSpec
的测量模式和尺寸。不过文中并没有针对 UNSPECIFIED
这种「想多大就多大」的模式进行详细探究,对于 UNSPECIFIED
相关可以查看这篇文章:每日一问:详细说一下 MeasureSpec.UNSPECIFIED。
至此,我们基本可以总结到:直接继承自 View 的自定义 View 都是建议重写 onMeasure() 方法的,并且我们一定不应该忽略 MeasureSpec.UNSPECIFIED 这个测量模式。因为在可滚动的布局中摆放我们这个自定义 View 的时候,通常需要处理这种「想多大就多大」的情况。
除此之外,我们在自定义 View 的时候,还可以注意的点有:
- 如果有自定义属性,在构造方法中及时对
TypeArray
进行recycler()
操作; -
onDraw()
和onTouchEvent()
方法中尽量避免创建对象或者进行耗时操作,如果过多出现会导致卡顿。 -
invalidate()
与postInvalidate()
都用于刷新 View,区别是invalidate()
可以在主线程调用,而postInvalidate()
可直接使用在子线程。 - 比较重要级的资源要释放的时候可以重写
onDetachedFromWindow()
并进行释放。
对于自定义 ViewGroup
而对于自定义 ViewGroup
,这里同样是只探讨直接继承自 ViewGroup
的自定义 ViewGroup
。我们在编写的时候,必须重写 onLayout()
并在其中编写子 View 摆放的逻辑,同样我们也就必须重写 onMeasure()
去测量每一个子 View 的尺寸进而来确定自己的尺寸。
在 onMeasure()
中遍历子 View,并显式调用它们的 measure()
方法进而触发子 View 的 onMeasure()
方法得到每一个子 View 的尺寸。如果要处理触摸事件的话,需要重写 onInterceptTouchEvent()
或者 onTouchEvent()
方法。
那么,自定义 ViewGroup 还有哪些需要我们注意的呢?
对于 LayoutParams
,我们在前面的 每日一问:关于 LayoutParams,你所应该知道的 一文中详细讲解了我们系统的 LayoutParams
的费心费力,讲到了为什么我们不设置甚至胡乱设置 LayoutParams
都不会导致崩溃的原因。所以我们在自定义 ViewGroup
的时候,最好也可以看看系统是怎么处理 LayoutParams
的。实际上,我们在自定义 ViewGroup 的时候最好重写下面四个方法。
- generateDefaultLayoutParams()
这个方法主要是在我们通过addView
添加子 View 到里面的时候,没有添加LayoutParams
的时候会回调进行处理。 - generateLayoutParams(attrs: AttributeSet?)
这个方法主要是用于我们在inflate()
View 的时候需要构造的一个LayoutParams
处理。 - checkLayoutParams(p: LayoutParams?)
这个方法主要是判断我们代码中设置的LayoutParams
是否正确,以防止我们在强转的时候发生异常。 - generateLayoutParams(p: LayoutParams?)
这个方法用于在我们外部设置错误的LayoutParams
时纠正为正确的LayoutParams
。
在自定义 ViewGroup 有触摸事件的时候,如果希望不影响子 View 的行为,需要重写 onInterceptTouchEvent()
方法去判断哪些行为是自己需要的,哪些是自己不需要的。
如果想要在 ViewGroup
中绘制一些东西,又没有在布局中设置 background
的时候,需要调用 setWillNotDraw(false)
进行处理。
可以还有许多,但这里由于时间关系就点到为止,总之,千万要注意 MeasureSpec.UNSPECIFIED,这个并没有诸多博客写到的很少见那么不重要。