最近看了一篇公众号推送的文章,关于Android刮奖效果的自定义控件,感觉蛮有意思,所以就模仿他也写了一个自定义ScratchView,人家的文笔不错,所以建议可以先看他的文章:ScratchView:一步步打造万能的 Android 刮奖效果控件;然后再来阅读我的,那为什么还要看我的呢?因为我针对内存优化和监听时机两个方面做了改进。
考虑如果你没看刚才那位的文章,那后面说的优化可能就云里雾里,所以还是说一下ScratchView的实现细节吧。ScratchView的实现思路就是通过绘制和被覆盖view一样大小但是不同透明度的蒙层,重写touch事件的响应来修改蒙层的透明度,实现刮奖的效果。
1.蒙层效果。
蒙层的本质是一个bitmap,通过不同颜色的画笔Paint,在onDraw的时候把bitmap绘制到画布Canvas上。下面这段是mask画笔的初始化,设置抗锯齿和防抖,最后是设置mask的颜色。
在view的宽和高确定之后,创建一个等大的bitmap,然后把这个bitmap绘制到画布上。
因为是自定义view,所以考虑可以在xml中设置这个view的一些属性,比如这里的蒙层的颜色属性。这里可以自定义它的一些属性,在attr.xml中增加这个ScratchView的属性,每条attr都是一个自定义属性,如下图。
在java代码中获取这些设置的属性,需要在构造函数中通过TypedArray来获取。需要考虑和属性的值的类型一一对应。
蒙层如果只是一种颜色,会比较单调,因此会考虑增加一些图案,比如logo在蒙层上面,这也称为水印效果。要实现水印效果,可以使用BitmapDrawable,利用它的TileMode来实现不同的水印效果。比如镜面对称的MIRROR,简单重复的REPEAT等。
现在我们展示一下,有蒙层的效果图。
2.刮奖效果。
刮奖效果本质是监听手指的touch事件,根据move事件,把经过的像素点的透明度设置为0,从而达到被擦除的效果。
新建一个擦除画笔,设置画笔的Xfermode模式,里面有一种PorterDuff.Mode.CLEAR模式,在很多画板程序里实现橡皮擦效果,这里也借鉴。
对touch事件的监听,只需要处理DOWN,MOVE和UP事件即可。
对于DOWN事件,需要重置擦除的路径,然后记录新的起点。对于MOVE事件,需要记录移动的路径,当然这里有最小的移动距离,然后利用擦除画笔,把路径上的点透明度修改掉,达到擦除效果。对于UP事件,需要重置擦除路径。
现在我们展示下擦除效果。
3.设置监听。
考虑到刮奖,当大部分的图案显示出来的时候,用户就不会继续刮下去,这时候,程序也应该有一个处理,提示中奖或者其他动作。因此,这里设置用户刮奖的时候,显示擦除部分的占比,当占比超过一个默认值时,就认为刮奖完成,需要给外面一个回调的接口。
已擦除部分和整个view的占比计算是一个比较重要的点。因为mask是用bitmap实现的,而bitmap是可以获取到每个像素点的透明度,所以可以通过一个数组存这个bitmap的每个像素点的透明度,每次擦除的时候都遍历一次,然后计算占比,就可以获得已擦除部分的占比。
mPixels就是mask的所有像素点透明度保存的数组,调用bitmap的getPixels可以获取当前bitmap的透明度并保存到这个int数组里面。通过计算已擦除数量mErasePixelCount和总的数量mTotalErasePixel的比例,得到实际占比。并把这个比例回调给监听者。
另外,这里是在touch事件的UP事件时候才处理是否擦除已完成的回调,而且通过设置mIsCompleted这个布尔值,保证回调只触发一次。
这个的效果需要配合其他观察者一起使用,就不展示了。
4.重置和清除效果。
重置的实现,本质是通过把mask的bitmap重新绘制一遍;同理清除效果本质是把mask的bitmap的所有像素点透明度置位0。
5.内存优化。
最初运行程序,每次重置都会发生内存稳定增加。如下图:
分析发现,因为重置是会调用createMaskBitmap函数,而这个函数内部重新new bitmap,同时还会把存bitmap像素点透明度的数组也会重新new一遍。
这里直接给出内存优化后的代码。
这里主要考虑一下两点:第一点,bitmap能不能不new,答案是肯定的,但是需要判断,如果view的size改变了,或者bitmap被回收了,就需要重新new,但是如果没有的话,我们直接复用之前的bitmap,不要再new。第二点,bitmap的像素点透明度的数组可不可以复用,答案也是肯定的,也只有在view的size发生变化的时候才需要去new。所以优化后的代码会增加这两个判断。
优化后的内存如下图所示,只有在第一次new bitmap的时候发生内存增加,后面操作基本没有发生内存比较大幅度的变化。
自定义的ScratchView的代码地址:刮奖效果控件--ScratchView