android 自定义view实现刮刮卡效果

本文记录一下自定义View实现刮刮卡效果的过程。

刮刮卡的实现主要是通过图像的混合来实现,但是在实现时有诸多细节需要注意。 通过分析生活中的刮刮卡效果,可以大概知道,灰色的蒙层是一层图像,而手势摸过的地方又是一层图像。 通过合适的混合模式就可以实现刮刮卡的效果。

我们知道手势可以通过path绘制到canvas上,那么如何将手势绘制的图像输出到一张图像上呢?
可以通过传入空的Bitmap到Canvas的构造函数中,然后将手势轨迹画到这个canvas上,那么该bitmap上就会有相关的轨迹了。

具体代码如下:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    mForeground = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    // 在 mPathCanvas 上绘制的元素均会输出到 mForeground  上。 
    mPathCanvas = new Canvas(mForeground);
//        mPathCanvas.drawColor(Color.GRAY);

    BitmapFactory.Options options = new BitmapFactory.Options();
    // 图像太大了,设置一下采样
    options.inSampleSize = 4;
    mBackground = BitmapFactory.decodeResource(getResources(), R.drawable.test, options);
}

在捕捉用户手势轨迹时,我们使用path及其二阶贝塞尔曲线来实现圆润的曲线。 (使用一阶时会有明显的转折痕迹,给人一种卡顿的现象)

@Override
public boolean onTouchEvent(MotionEvent event) {
    int action = event.getAction();
    switch (action) {
        case MotionEvent.ACTION_DOWN:
        	// 将path移动到触摸点
            mPath.moveTo(event.getX(), event.getY());
            lastX = event.getX();
            lastY = event.getY();
            break;
        case MotionEvent.ACTION_MOVE:
        	// 计算控制点,为当前点与前一个点的中点
            float tx = (lastX + event.getX()) / 2;
            float ty = (lastY + event.getY()) / 2;
            // 实现二阶贝塞尔曲线
            mPath.quadTo(lastX, lastY, tx, ty);

            lastX = event.getX();
            lastY = event.getY();
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
//                new Thread(mCalcPixel).start();
            break;
    }
    invalidate();
    return true;
}

但是此时,还没有将手势绘制到bitmap上。由于这是一个动态的绘制过程,我们在onDraw中实现。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // 文字绘制的坐标,如何居中
    canvas.drawText("一等奖", 50, 50, mTextPaint);

	// 是否全部显示底层内容。 
    if (mIsComplete) {
        return;
    }
    // save与savelayer的区别,此处用save就不行。 save只保存了MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG 这两个信息
    // 而图像的混合用到了alpha通道。 
    int layerId = canvas.saveLayer(new RectF(0, 0, getWidth(), getHeight()), mPaint, Canvas.ALL_SAVE_FLAG);

	// 将手势绘制到图片上,得到bitmap
    mPathCanvas.drawPath(mPath, mPaint);
    // 绘制手势图像
    canvas.drawBitmap(mForeground, 0, 0, mPaint);
	
	// 可以简记为 设置xfermode前的是目标图像, 之后绘制的是源图像
    mPaint.setXfermode(xfermode);

	// 绘制遮罩图
    canvas.drawBitmap(mBackground, 0, 0, mPaint);
    mPaint.setXfermode(null);
    canvas.restoreToCount(layerId);
}

想像一下,由于手势绘制的地方要漏出底层,故手触摸的地方与背景图相交时不显示,不相交时显示遮罩图。 按照上面的代码顺序,我们得用的混合模式为src_out。 定义如下:

private PorterDuffXfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT);

如上便可基本实现一个刮刮卡的效果了。 但是我经常在支付宝中看到,他们可以做到在刮开一定面积后就会自动的显示刮将结果 。他们是如何实现的呢? 在网上查看了一些博客(参考此篇),原来是通过像素计算刮开的面积来实现的。

基本实现如下:

// 标识是否全部显示
private  boolean mIsComplete;
private Runnable mCalcPixel = new Runnable() {
    @Override
    public void run() {
    	// 注意,此处计算的是手势图片(即在手势图片上画了多少像素)
        Bitmap bitmap = mForeground;    // 计算图片被抹去了多少

        int w = mForeground.getWidth();
        int h = mForeground.getHeight();

        int wipeArea = 0;
        int totalArea = w * h;
        int[] mPixelArr = new int[w * h];
        bitmap.getPixels(mPixelArr, 0, w, 0, 0, w, h);
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                int index = i + j * w;
                // 此处只能以 0 和 非0来计算。
                if (mPixelArr[index] != 0) {
                    wipeArea++;
                }
            }
        }

        if (wipeArea > 0 && totalArea > 0) {
            // 这里注意,先用浮点数计算,然后再转整型。 否则,int计算出来的一直是0
            int percent = (int) (wipeArea * 1.0f / totalArea * 100);
            if (percent > 50) {
                mIsComplete = true;
                postInvalidate();
            }
        }

    }
};

由于计算量比较大,故一般放入子线程中处理。 上篇参考文章中,该计算是放在move事件中计算中的。感觉计算量有点大,此处放在up或cancel事件中效果也还可以。

此自定义view需要注意的事项:
1、混合是针对图片的, 需要先将手势轨迹输出到图像上,从而与特定的图片混合实现刮的效果。
2、手势的输出使用path及二阶贝塞尔曲线,并注意move时控制点的计算。
3、混合时不能使用canvas.save()方法,而应该用saveLayer方法。因为save方法只保存了matrix及clip信息,而没alpha通道相关信息。
4、刮开面积的比例是通过获取bitmap的像素计算的,注意如果某个像素点为0则为未绘制,非0为绘制。
5、在调试时可以通过Bitmap.compressed方法将bitmap输出到文件查看。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值