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

时间:2023-02-09 08:58:12

感觉好久没写博客了,最近也是很烦,现在可以要好好学习,把自己的心态调整好,

今天实现一个刮刮卡效果,主要用到的知识点还是canvas和paint,Path这几个类,不熟悉的可以先把这三个中用到什么方法,熟悉下,也就是把api熟悉了,知道它是干嘛用的,


其实刮刮卡和橡皮擦的功能很像,我之前博客也写过关于android涂鸦方面的,如果那个看懂了,刮刮卡就很容易看的懂,我还是先写一个简单的涂鸦功能,

package com.simple;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* Created by admin on 2017/5/3.
*/
public class ScratchCardView extends View {
private int width;
private int height;
private String text = "猜猜她是谁";
private Paint paint;
private Path path;
private float downX,downY;
private float tempX,tempY;
public ScratchCardView(Context context) {
this(context,null);
}

public ScratchCardView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}

public ScratchCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint();
}

private void initPaint() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2);
paint.setTextSize(24);
path = new Path();
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path,paint);
}
private void drawText(Canvas canvas) {
Rect rect = new Rect();
paint.getTextBounds(text, 0, text.length(), rect);
int textWidth = rect.width();
int textHeight = rect.height();
float x = width/2-textWidth/2;
float y = height/2+textHeight/2;
paint.setColor(Color.parseColor("#9370DB"));
canvas.drawText(text,x,y,paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
path.moveTo(downX,downY);
invalidate();
tempX = downX;
tempY = downY;
break;
case MotionEvent.ACTION_MOVE:
float moveX = event.getX();
float moveY = event.getY();
path.quadTo(tempX,tempY,moveX,moveY);
invalidate();
tempX = moveX;
tempY = moveY;
break;
}
return true;
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/activity_main"    android:layout_width="match_parent"    android:layout_height="match_parent"    >  <com.simple.ScratchCardView      android:layout_width="540dp"      android:layout_height="300dp"      android:layout_centerInParent="true"      android:background="@drawable/bg"      ></com.simple.ScratchCardView></RelativeLayout>
效果图:

android 自定义view实现刮刮卡效果
这个就是刮刮卡效果中的擦除功能,只是你给paint设置的颜色不同而已,

比如如何创建一个可绘制的空的画布canvas呢?

上面我们是在系统的canvas上去绘制一些文字的,但是要在自己创建的画布上绘制东西,怎么创建?其实很简单

 myBitmap = Bitmap.createBitmap(700,400, Bitmap.Config.ARGB_8888);
 myCanvas = new Canvas(myBitmap);

这就二行代码就可以了,然后通过系统的canvas,把这个mBitmap绘制上去就ok,比如怎么把几张图合并成一张图,就这么这么干了,下面四张图是我叫美工切的:

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

然后通过代码把这四张图合并成一张图入下:

package com.simple;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* Created by admin on 2017/5/3.
*/
public class ScratchCardView extends View {
private static final String TAG ="ScratchCardView" ;
private int width;
private int height;
private String text = "猜猜她是谁";
private Paint paint;
private Path path;
private float downX,downY;
private float tempX,tempY;
private Canvas myCanvas;
private Bitmap myBitmap;
private Bitmap bitmapa;
private Bitmap bitmapb;
private Bitmap bitmapc;
private Bitmap bitmapd;
public ScratchCardView(Context context) {
this(context,null);
}

public ScratchCardView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}

public ScratchCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initBitmap();
initPaint();
}

private void initBitmap() {
bitmapa = BitmapFactory.decodeResource(getResources(),R.drawable.a);
bitmapb = BitmapFactory.decodeResource(getResources(),R.drawable.b);
bitmapc = BitmapFactory.decodeResource(getResources(),R.drawable.c);
bitmapd = BitmapFactory.decodeResource(getResources(),R.drawable.d);
}

private void initPaint() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2);
paint.setTextSize(24);
paint.setDither(true);
path = new Path();
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
myBitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
myCanvas = new Canvas(myBitmap);
}
@Override
protected void onDraw(Canvas canvas) {
drawBitmap();
drawPath();
canvas.drawBitmap(myBitmap,0,0,null);
super.onDraw(canvas);
}
private void drawPath() {
myCanvas.drawPath(path,paint);
}
private void drawBitmap() {
myCanvas.drawBitmap(bitmapa,0,0,null);
myCanvas.drawBitmap(bitmapb,bitmapa.getWidth(),0,null);
myCanvas.drawBitmap(bitmapc,0,bitmapa.getHeight(),null);
myCanvas.drawBitmap(bitmapd,bitmapc.getWidth(),bitmapb.getHeight(),null);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
path.moveTo(downX,downY);
invalidate();
tempX = downX;
tempY = downY;
break;
case MotionEvent.ACTION_MOVE:
float moveX = event.getX();
float moveY = event.getY();
path.quadTo(tempX,tempY,moveX,moveY);
invalidate();
tempX = moveX;
tempY = moveY;
break;
}
return true;
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/activity_main"    android:layout_width="match_parent"    android:layout_height="match_parent"    >  <com.simple.ScratchCardView      android:layout_width="450dp"      android:layout_height="360dp"      android:layout_centerInParent="true"      ></com.simple.ScratchCardView></RelativeLayout>
我们看到布局文件中没有什么背景图片了,说明这是合并成的:

android 自定义view实现刮刮卡效果
合并的原理其实很简单:

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

如果做过tv端的,对倒影会有点影响,其实就是根据这个原理做的,这也是拼图小游戏中的一个小功能。还有是不是类似明星给你在衣服上签名一样.

现在我们更进一步就是怎么在一张被盖住的图片上进行擦除,比如底层是一张图片,这图片上层有一层背景色,然后把上层的背景色擦除就可以看到下层的美女了,激动吧!

package com.simple;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* Created by admin on 2017/5/3.
*/
public class ScratchCardView extends View {
private static final String TAG ="ScratchCardView" ;
private int width;
private int height;
private String text = "猜猜她是谁";
private Paint paint;
private Path path;
private float downX,downY;
private float tempX,tempY;
private Canvas myCanvas;
private Bitmap myBitmap;
private Bitmap bitmapa;
private Bitmap bitmapb;
private Bitmap bitmapc;
private Bitmap bitmapd;
private Bitmap bitmap;
public ScratchCardView(Context context) {
this(context,null);
}

public ScratchCardView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}

public ScratchCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initBitmap();
initPaint();
}
private void initBitmap() {
bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.bg);
bitmapa = BitmapFactory.decodeResource(getResources(),R.drawable.a);
bitmapb = BitmapFactory.decodeResource(getResources(),R.drawable.b);
bitmapc = BitmapFactory.decodeResource(getResources(),R.drawable.c);
bitmapd = BitmapFactory.decodeResource(getResources(),R.drawable.d);
}
private void initPaint() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(20);
paint.setTextSize(24);
paint.setDither(true);
path = new Path();
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
myBitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
myCanvas = new Canvas(myBitmap);
myCanvas.drawColor(Color.GREEN);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(bitmap,0,0,null);//底层图
drawPath();//这个path是绘制在myvanvas画布上的
canvas.drawBitmap(myBitmap,0,0,null);
super.onDraw(canvas);
}
private void drawPath() {
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
myCanvas.drawPath(path,paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
path.moveTo(downX,downY);
invalidate();
tempX = downX;
tempY = downY;
break;
case MotionEvent.ACTION_MOVE:
float moveX = event.getX();
float moveY = event.getY();
path.quadTo(tempX,tempY,moveX,moveY);
invalidate();
tempX = moveX;
tempY = moveY;
break;
}
return true;
}
}
效果图:

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

这看起来是不是离呱呱卡效果近了,其实完全可以做一个最简单的刮刮卡,就是把文字和低层图片放在一起,

我现在的底图是:

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

上面的代码一点都没动,然后效果是这样的:

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

这样做很low,而且实际效果也不好,但却是一种实现方案.能实现这个效果最关键的一点是paint中的一个方法:

paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));

这是设置了paint的图形混合模式,这个PorterDuff.Mode的类中有很多个变量,大概十几种吧,具体每个什么意思,最好是自己写demo去体会,光记住是没用的.

DST_OUT:只在源图像和目标图像不相交的地方绘制目标图像,在相交的地方根据源图像的alpha进行过滤,源图像完全不透明则完全过滤,完全透明则不过滤

我们这个paint画笔是作用于Path上的,path是我们要绘制的目标图像,下面的底图是源图像,

在这里我画个图更好理解了:

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

这样刮刮卡我们只要把图片去掉,换成绘制的文字就ok了,

整个代码如下:

package com.simple;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* Created by admin on 2017/5/3.
*/
public class ScratchCardView extends View {
private static final String TAG ="ScratchCardView" ;
private int width;
private int height;
private String text = "苍老师晚上约你";
private Paint textPaint;
private Paint paint;
private Path path;
private float downX,downY;
private float tempX,tempY;
private Canvas myCanvas;
private Bitmap myBitmap;
public ScratchCardView(Context context) {
this(context,null);
}

public ScratchCardView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}

public ScratchCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint();
}
private void initPaint() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(6);
paint.setTextSize(24);
paint.setDither(true);
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setStrokeWidth(20);
textPaint.setTextSize(24);
textPaint.setDither(true);
textPaint.setColor(Color.BLACK);
path = new Path();
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
myBitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
myCanvas = new Canvas(myBitmap);
myCanvas.drawColor(Color.GREEN);
}
@Override
protected void onDraw(Canvas canvas) {
// canvas.drawBitmap(bitmap,0,0,null);//底层图
drawText(canvas);
drawPath();//这个path是绘制在myvanvas画布上的
canvas.drawBitmap(myBitmap,0,0,null);
super.onDraw(canvas);
}

private void drawText(Canvas canvas) {
Rect rect = new Rect();
textPaint.getTextBounds(text, 0, text.length(), rect);
int textWidth = rect.width();
int textHeight = rect.height();
float x = width/2-textWidth/2;
float y = height/2+textHeight/2;
canvas.drawText(text,x,y,textPaint);
}
private void drawPath() {
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
myCanvas.drawPath(path,paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
path.moveTo(downX,downY);
invalidate();
tempX = downX;
tempY = downY;
break;
case MotionEvent.ACTION_MOVE:
float moveX = event.getX();
float moveY = event.getY();
path.quadTo(tempX,tempY,moveX,moveY);
invalidate();
tempX = moveX;
tempY = moveY;
break;
}
return true;
}
}
效果:

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

github:https://github.com/zhouguizhi/scratchcard