今天要分享的是Android上的刮刮卡控件。按照国际惯例,先上效果图。
主要的功能:
- 自定义刮开后的文字、图片;
- 自定义覆盖层的颜色、图片;
- 刮开大部分刮涂层后,自动清除剩余刮涂层;
按照国际惯例,线上效果图:
源码放在最后
一、实现思路
- 绘制奖区文字;
- 绘制刮涂层;
- 监听用户touch事件,跟随用户touch轨迹清除挂涂层对应位置的像素;
- 监听挂涂层剩余像素,当达到一个阈值时,清空所有像素。
二、绘制奖区文字
首先需要new一个专门负责绘制文字的paint。然后后面根据自定义属性设置的文字大小、颜色,以及奖区的内容,使用canvas.drawText()方法去绘制文字。
但是要把文字绘制在刮刮卡的正中间,就需要我们自己去计算drawText的起始点坐标。这里的思路是:
- 在onMeasure中获取整个刮刮卡的高和宽;
- 计算要显示的文字所占的整个Rect的高和宽;
- 用整个(刮刮卡的宽 - 文字所占的宽)/2得到x轴的起始坐标;
- 用整个(刮刮卡的高 - 文字所占的高)/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属性,也就是图像的混合模式绘制出来。
/**
* 绘制用户手指刮过的路径
*/
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();
}
}
}
};
最后献上源码,有任何问题的朋友,欢迎在下面留言。
源码下载