本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
最近学习了一些自定义控件的知识,想着趁热多做些练习来巩固,上周自定义了一个等级进度条,是一个自定义View,这周就换一个类型,做一个自定义的ViewGroup。这周自定义ViewGroup的是一个锁屏控件,效果如下:
效果分析:
仔细分析效果图发现,锁屏控件需要绘制的有三个部分,分别是:
1.图案点,图案点有四种状态,分别是默认、选中、正确和错误
2.图案点之间的连线
连线会根据1中点的状态改变发生颜色上的变化
3.悬空线段
就是图案点和悬空点之间的线段
整体思路:
1.自定义一个LockScreenView来表示图案点,LockScreenView有四种状态
2.自定义一个LockScreenViewGroup,在onMeasure中获取到宽度以后(根据宽度算图案点之间的间距),动态地将LockScreenView添加进来
3.在LockScreenViewGroup的onTouchEvent中消耗触摸事件,根据触摸点的轨迹来更新LockScreenView、图案点连线和悬空线段
实现:
1.自定义LockScreenView
由于没有和这个自定义View比较类似的原生控件,因此自定义的时候直接继承自View
首先,需要的属性通过构造函数传入:
View的状态用一个枚举类型来表示:
View的状态通过暴露一个方法给LockScreenViewGroup来进行设置。
在onDraw方法中判断类型,进行绘制:
这里在选中时用属性动画做了一个放大效果,在下次恢复正常的时候要将大小恢复回去:
在LockScreenViewGroup中,我将LockScreenView的宽高设置为wrap_content,因此需要在onMeasure方法做一些特殊的处理,至于为什么要做特殊处理,在上一篇博文《等级进度条》中已经提到过了。
2.自定义LockScreenViewGroup
为了方便确定子View的位置,LockScreenViewGroup继承自RelativeLayout
在xml中赋予了如下属性:
其中itemCount表示一行有几个LockScreenView,其它属性都已经提到过了
在构造函数中解析xml中的自定义属性:
在onMeasure方法中,获取到LockScreenViewGroup的宽以后,算出LockScreenView之间的间隙,并动态地将LockScreenView添加进来(每个LockScreenView添加进来的时候,设置id作为唯一标识,后面在判断图案是否正确时会用到):
这里有两个地方需要注意一下:
1.LockScreenView的宽不能用getMeasuredWidth方法来获取,因为这里只是把LockScreenView创建了出来,还没有对它进行测量,故通过getMeasuredWidth方法只能得到0,这里直接把LockScreenView中大圆的直径当作它的宽(因为这里动态添加的时候用了wrap_content, 并且没有设padding)
2.重写onMeasure方法的时候不能把super.onMeasure方法删掉,因为这里面会进行子View宽高的测量,删了子View就画不出来了
触摸事件的消耗在onTouchEvent中处理(在这个案例中也可以在dispatchTouchEvent方法中处理,因为子View的状态由LockScreenViewGroup告诉它了,子View不需要处理触摸事件)。
在onTouchEvent方法中对Down、Move、Up三种不同的触摸状态分别做了处理。
(1)首先,在Down状态时,需要对之前的状态做一些重置:
其中,mCurrentViews用来保存当前选中的LockScreenView的id,mCurrentPath用来保存图像点间线段的路径,skyStartX、skyStartY分别是悬空线段起始的x和y。
(2)在Move状态时,判断是否在LockScreenView区域,如果在某个LockScreenView区域且这个LockScreenView之前没有被选中,则将这个LockScreenView设置为选中状态。另外在onMove中还做了图案点间线段路径和悬空线段起点和终点(mTempX、mTempY)的更新,悬空线段的起点就是上一个被选中的LockScreenView的中心点。
(3)在Up状态时,根据答案的正确与否,对LockScreenView设置不同的状态,并且对悬空线段起始点进行重置
在onTouchEvent方法最后会调用invalidate方法对视图进行重绘,这时会调用dispatchDraw方法进行子View的绘制。
在dispatchDraw方法中进行图像点间的线段路径以及悬空线段的绘制:
这里要注意,在重写dispatchDraw方法时,不能把super.dispatchDraw方法删掉,因为这里会绘制LockScreenViewGroup的子View(即,LockScreenView们),如果删了,动态添加的LockScreenView就会显示不出来(重写的时候不小心删了,排查好久才发现是这里的问题,都是泪orz)
最后附上github源码地址:LockScreenVIew