轻轻松松实现刮刮卡效果

2,005 阅读6分钟

已经好几天没有写文章了,最近一直在研究如何自定义“下拉刷新”控件。我日啊,对于我来说太难了,看了好几天SwipeRefreshLayout的源码,一脸懵逼,自己的demo也是写到一半就写不下去了。等改天有时间了再去研究研究。今天是周末,白天学了点新知识,晚上出去练了会儿舞(不是广场舞),现在在写博客,等下就睡了。

有人问,为什么现在开源的自定义View这么多,我们还要自己辛辛苦苦去造轮子。有个博主曾经说过,我觉得说得很好。他说,现在很多的开源库适用性比较广,我们如果去使用它,你会发现,很多功能我们是用不到的,而且会让程序包增大。不是自己写的代码,维护起来也是十分困难,万一哪天老板让你改一下里面的东西,你就懵逼了。




之前一直想如何实现一个刮刮卡效果,但是能力有限,所以就一直拖着。今天下午闲着没事,就随便敲了敲代码,一运行,发现成功了,我擦!当时也是很惊讶,并没有感到很难的样子,但是之前确实感觉挺难的。不管怎么说,既然做出来了,就教大家怎么来实现这样的功能吧。

先看一下运行效果:



不知道为什么发不了动图,想看动图的可以去我的Github上观看,Github地址会在文末给出。当然,如果你是个热血青年,想亲自感受下这个控件的试用效果,那你可以扫描下方二维码,直接把Demo下载到手机上运行就好了。😁😁😁走过路过不要错过。强烈推荐大家下载Demo,里面还包含了很多我之前写的案例,而且我会一直更新下去。放心下载吧,里面没有鸡汤文,长老保证,都是技术干货~~~


demo下载地址

具体的实现思路是这样的:我们需要一个背景和一个前景,背景就是隐藏在刮刮卡后面的东西,可能是图片,也可能是百万大奖,前景就是灰色的遮盖层了。然后还需要一支画笔,画笔在前景上面会形成轨迹,我们让轨迹透明就好了,这样就能看到下面的东西了。

废话不多说,开始写代码。首先,我们自定义一个GuaGuaKa类,继承自View,并实现其构造方法,看代码:

public class GuaGuaKa extends View {

    private Paint pathPaint;

    private Path path;

    private Bitmap cacheBitmap;
    private Canvas cacheCanvas;

    private Bitmap backBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.demo);

    public GuaGuaKa(Context context) {
        this(context, null);
    }

    public GuaGuaKa(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.setBackgroundColor(Color.WHITE);

        pathPaint = new Paint();
        pathPaint.setAntiAlias(true);
        pathPaint.setDither(true);
        pathPaint.setColor(Color.BLACK);
        pathPaint.setStyle(Paint.Style.STROKE);
        pathPaint.setStrokeWidth(50);
        pathPaint.setStrokeCap(Paint.Cap.ROUND);
        pathPaint.setStrokeJoin(Paint.Join.ROUND);

        path = new Path();
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        cacheBitmap = Bitmap.createBitmap(getWidth() - getPaddingLeft() - getPaddingRight(), getHeight() - getPaddingTop() - getPaddingBottom(), Bitmap.Config.ARGB_8888);
        cacheCanvas = new Canvas(cacheBitmap);
    }
}

在构造方法里面,我对paint的属性进行了初始化。如果对Paint的用法还不是很熟悉的同学,可以参考的另外一篇文章《Paint特效大全,看这一篇就够了!》
在onLayout方法里面,我对bitmap和canvas进行了初始化。我想问一下同学们,为什么我不直接在构造方法里初始化?想知道的同学可以继续往下看。
按常理来说,bitmap和canvas的初始化是完全可以放在构造方法里的,我也真心希望可以这么做,不然这里一块代码,那里一块代码,以后代码多了,找起来还不方便。
理想很丰满,现实很骨感!不知道大家注意到没有,cacheBitmap在初始化的时候需要传入getWidth( ),这个方法的返回值就是整个GuaGuaKa控件的宽度,而这个值必须在onMeasure方法执行完之后才能有结果的,在onMeasure没有执行之前都是返回0。为什么我会知道这个结论呢?因为我学过View的绘制机制啊。如果你听不懂,就应该去学习一下View的绘制机制了。
第二步,我们需要处理手指的滑动事件,重写onTouchEvent方法就好了。前提是,你必须要学过Android Touch事件分发机制,没学过的同学,下面的代码基本上是看不懂的。是不是感觉很操蛋,一个简简单单的自定义View,居然要学习那么多android运行机制。
来,看一下代码

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            pointerX = event.getX();
            pointerY = event.getY();
            path.moveTo(pointerX, pointerY);
            break;
        case MotionEvent.ACTION_MOVE:
            path.lineTo(event.getX(), event.getY());
            pointerX = event.getX();
            pointerY = event.getY();
            break;
    }
    invalidate();
    return true;
}

当手指第一次按下的时候,记录下x坐标和y坐标,并让path移动到这个坐标。然后在手指移动过程中不断地改变path的路径,然后不断地去重绘,这样才能达到实时展示的效果。有些博客绘制path用的是path.quadTo(),我这里用的是lineTo,其实是有差别的。但我们这里不讨论,行吧。虽然我这里不讨论,但是不知道的同学们还是有必要去学习一下quadTo和lineTo的区别的。

既然调用invalidate了,那系统肯定会去执行onDraw方法,我很好奇,onDraw方法会去执行什么代码?
直接看代码:

@Override
protected void onDraw(Canvas canvas) {
    //画背景
    canvas.drawBitmap(backBitmap, 0, 0, null);
    //利用PorterDuff.Mode画线条,重叠模式为DST_OUT
    //灰色背景和path的重叠
    cacheCanvas.drawColor(Color.GRAY);
    pathPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
    cacheCanvas.drawPath(path, pathPaint);
    //画前景
    canvas.drawBitmap(cacheBitmap, 0, 0, null);
}

注释写得很清楚,先画背景,再画前景。背景就是一张图片,前景就是灰色遮盖层和path轨迹的结合体,结合规则是DST_OUT,给大家看一张图,你们就知道DST_OUT的效果是什么了。



说实话,我刚开始觉得应该用Xor模式,怎么都想不通为何要用DstOut模式。到现在还没想明白,擦。。。。。。

一个刮刮卡就完成了,看起来很简单,代码量很少,其实需要写出来这样的效果确实需要一定的功底,你需要学会android事件分发机制,View的绘制机制,还有paint的特效使用,Path的使用,Canvas的使用,还要学会Bitmap相关的知识,太他妈难了!!!我也是一步步学过来的,过程很艰辛,但是当你把东西做出来之后,你会很有成就感。

最后呢,你们还是需要去自己实践一下的,把代码写出来,然后简单分析一下原因。实在懒的人可以去我的Github上拷贝一下代码,这篇博客的代码不是很完整,重点讲解思路,完整的代码在这里:github.com/Elder-Wu/No…
如果觉得我写得好,可以点个赞,然后关注一下的哦~另外,我的微信公众号也开播了,每天都会分享一篇优质的技术文章,欢迎大家来踩点。(公众号:代码也是人)



技术交流群:471395156(欢迎大家入坑)