Android自定义刮刮卡刮奖

今天要分享的是Android上的刮刮卡控件。按照国际惯例,先上效果图。
主要的功能:

  1. 自定义刮开后的文字、图片;
  2. 自定义覆盖层的颜色、图片;
  3. 刮开大部分刮涂层后,自动清除剩余刮涂层;

按照国际惯例,线上效果图:


刮刮卡.gif

源码放在最后

一、实现思路

  1. 绘制奖区文字;
  2. 绘制刮涂层;
  3. 监听用户touch事件,跟随用户touch轨迹清除挂涂层对应位置的像素;
  4. 监听挂涂层剩余像素,当达到一个阈值时,清空所有像素。

二、绘制奖区文字

首先需要new一个专门负责绘制文字的paint。然后后面根据自定义属性设置的文字大小、颜色,以及奖区的内容,使用canvas.drawText()方法去绘制文字。

但是要把文字绘制在刮刮卡的正中间,就需要我们自己去计算drawText的起始点坐标。这里的思路是:

  1. 在onMeasure中获取整个刮刮卡的高和宽;
  2. 计算要显示的文字所占的整个Rect的高和宽;
  3. 用整个(刮刮卡的宽 - 文字所占的宽)/2得到x轴的起始坐标;
  4. 用整个(刮刮卡的高 - 文字所占的高)/2得到y轴的起始坐标;
//计算奖区文字区域的大小,用于后面计算奖区文字起始点的坐标
mPrizeTextPaint.setColor(mPrizeTextColor);
mPrizeTextPaint.setTextSize(mPrizeTextSize);
mTextBound = new Rect();
 mPrizeTextPaint.getTextBounds(mPrizeContent, 0, mPrizeContent.length(), mTextBound);
 //绘制奖区内容,这里要绘制在整个View的正中间.
canvas.drawText(mPrizeContent, getWidth() / 2 - mTextBound.width() / 2, 
                  getHeight() / 2 + mTextBound.height() / 2, mPrizeTextPaint);

三、绘制刮涂层

这个是绘制整个控件的重点。这里的思路是new一个bitmap对象,把刮涂层涂层以及后面用户手指刮开的路径混合在一起,然后通过canvas绘制到这个bitmap上。最后在onDraw的时候,使用整个控件的canvas对象去绘制出这个bitmap对象,从而实现刮涂效果。

先在onMeasure中计算出整个刮刮卡的宽和高,然后new一个一样大小的bitmap对象。使用一个canvas对象来绘制到这个bitmap中。

        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        //初始化刮涂层的画布,并将刮涂层的内容全部保存到bitmap对象中。
        //最后在onDraw中调用控件的canvas去绘制这个bitmap,从而实现刮奖的效果。
        mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        mCoverCanvas = new Canvas(mBitmap);
        mCoverCanvas.drawColor(Color.parseColor("#c0c0c0"));//先draw src,后面会draw dist

监听用户擦除的路径。通过重写onTouchEvent事件。通过一个Path对象来记录用户擦除的痕迹。每touch一次,就在Path上增加一条。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastX = x;
                mLastY = y;
                mTouchPath.moveTo(mLastX, mLastY);
                break;
            case MotionEvent.ACTION_MOVE:
                mTouchPath.lineTo(event.getX(), event.getY());
                float dx = Math.abs(x - mLastX);
                float dy = Math.abs(y - mLastY);
                if (dx > 3 || dy > 3) {
                    mTouchPath.lineTo(x, y);
                }
                mLastX = x;
                mLastY = y;
                break;
            case MotionEvent.ACTION_UP:
                //计算已经刮完的像素
                if (!isCompleted)
                    new Thread(mRunnable).start();
                break;
        }
        if (!isCompleted)
            invalidate();
        return true;//消费掉touch事件
    }

然后设置涂层画笔的Xfermode属性,也就是图像的混合模式绘制出来。

paint的Xfermode属性的使用,参照下面这张图
paint.Xfermode
然使用的时候遵循先画Src,设置Xfermode属性,最后画Dist的顺序来把用户刮涂轨迹Path对象给draw到之前一张没有被刮的涂层上去。
    /**
     * 绘制用户手指刮过的路径
     */
    private void drawPath() {
        mCoverPaint.setStyle(Paint.Style.STROKE);
        mCoverPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
        mCoverCanvas.drawPath(mTouchPath, mCoverPaint);
    }

经过上面的这些操作,我们定义的bitmap就成了灰色涂层和用户触摸轨迹混合的一个对象了。最后就在onDraw中绘制到canvas中就可以了。

canvas.drawBitmap(mBitmap, 0, 0, null);

四、监听刮涂的面积,当达到一定阈值的时候,自动清空剩余的涂层

这里就需要在用户每次在otionEvent.ACTION_UP的时候去判断我们涂层的bitmap对象有多少像素的像素值为0了(像素值为0代表透明)。然后计算一下透明的像素数量占总像素的百分比。如果这个百分比超过阈值,那么再重新绘制到额时候,就不去绘制挂涂层,就可以实现清空剩余挂涂层的效果了。

为了不因为计算这个像素数量而引起UI线程阻塞,我们另开一个线程来计算。同时设置一个volatile修饰的布尔对象来标识是否达到阈值。主线程通过这个标识来判断是否需要绘制挂涂层。

    private Runnable mRunnable = new Runnable()
    {
        @Override
        public void run() {
            int w = getWidth();
            int h = getHeight();

            float wipeArea = 0;
            float totalArea = w * h;
            Bitmap bitmap = mBitmap;
            int[] mPixels = new int[w * h];

            bitmap.getPixels(mPixels, 0, w, 0, 0, w, h);
            //计算被擦除的区域(也就是像素值为0)的像素数之和
            for (int i = 0; i < w; i++) {
                for (int j = 0; j < h; j++) {
                    int index = i + j * w;
                    if (mPixels[index] == 0) {
                        wipeArea++;
                    }
                }
            }
            //计算擦除的像素数与总像素数的百分比
            if (wipeArea > 0 && totalArea > 0) {
                int percent = (int) (wipeArea * 100 / totalArea);
                if (percent > 60) {
                    isCompleted = true;
                    postInvalidate();
                }
            }
        }
    };

最后献上源码,有任何问题的朋友,欢迎在下面留言。
源码下载

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容