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属性的使用,参照下面这张图
Android自定义刮刮卡刮奖_第1张图片
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();
                }
            }
        }
    };

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

你可能感兴趣的:(Android自定义刮刮卡刮奖)