PorterDuffXfermode 这个类在以前的博客中就已经使用过,见《
利用 2D 图形和 PorterDuffXferMode 等实现被遮罩的图片
》
http://blog.youkuaiyun.com/antimage08/article/details/50396931。
PorterDuffXfermode 相关的可以参见 API Demo 中图,如下:
它控制的是两个图像间的混合显示模式。
这里需要注意的是:PorterDuffXfermode 设置的是两个图层交集区域的显示方式,dst 是先画的图形,而 src 是后画的图形。其中最常用的就是通过 DST_IN、SRC_IN 模式来实现将一个矩形图片变成圆角或者圆形图片的效果。如前面提到的那篇博客一样。
现在要实现的效果如下:
刮刮卡一般都有两层,上面一层是用来刮掉的的图层,下面一层是隐藏的图层。在初始状态下,上面的图层会将下面整个的图层覆盖,当你用手刮上面的图层的时候,下面的图层会慢慢的显示出来,这也类似于很多图画工具中的橡皮檫效果。这个效果就可以用PorterDuffXfermode 来实现。
第一个效果的实现:
初始化工作,比如准备好图片,设置好 Paint 的一些属性。
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeWidth(50);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPath = new Path();
mBgBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.dog);
mFgBitmap = Bitmap.createBitmap(mBgBitmap.getWidth(),
mBgBitmap.getHeight(), Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mFgBitmap);
// 覆盖层的(可见层的)的颜色
mCanvas.drawColor(Color.GRAY);
其中:Paint.Join.ROUND 和 Paint.Cap.ROUND 属性是让 Paint 的笔触和连接处能更加的圆滑一点。
通过滑动来产生路径,可以用贝塞尔曲线来优化,但是这里只是用一般的效果(贝塞尔曲线都暂时不记得怎样做了)。
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPath.reset();
mPath.moveTo(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
mPath.lineTo(event.getX(), event.getY());
break;
}
mCanvas.drawPath(mPath, mPaint);
// 通知重绘,不然不会出现被挂过后的痕迹
invalidate();
return true;
最后只需要使用 DST_IN 模式将路径绘制到前面的覆盖层上即可,整个完整的代码如下:
GuaGuaKa.java :
package com.crazy.guaguaka;
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;
public class GuaGuaKa extends View {
// mBgBitmap 为底层图片,mFgBitmap 为覆盖层图片
private Bitmap mBgBitmap, mFgBitmap;
private Paint mPaint;
private Canvas mCanvas;
private Path mPath;
public GuaGuaKa(Context context) {
this(context, null);
}
public GuaGuaKa(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GuaGuaKa(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
// 由于使用 PorterDuffXfermode 进行图层混合,并不是简单的只进行图层的计算,
// 同时也会计算透明通道的值;所以设置透明度为0。
mPaint.setAlpha(0);
mPaint.setXfermode(
new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeWidth(50);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPath = new Path();
mBgBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.dog);
mFgBitmap = Bitmap.createBitmap(mBgBitmap.getWidth(),
mBgBitmap.getHeight(), Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mFgBitmap);
// 覆盖层的(可见层的)的颜色
mCanvas.drawColor(Color.GRAY);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPath.reset();
mPath.moveTo(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
mPath.lineTo(event.getX(), event.getY());
break;
}
mCanvas.drawPath(mPath, mPaint);
// 通知重绘,不然不会出现被挂过后的痕迹
invalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBgBitmap, 0, 0, null);
canvas.drawBitmap(mFgBitmap, 0, 0, null);
}
}
其中关键的一步是:将画笔的透明度设置为 0 ,这样才能显示出擦除的效果。 由于使用 PorterDuffXfermode 进行图层混合,并不是简单的只进行图层的计算,同时也会计算透明通道的值;所以设置透明度为0。使用 PorterDuffXfermode 在绘图时,最好关闭硬件加速。
第二个效果的实现:
该代码出自于 鸿洋 大神的博客《Android 自定义控件实现刮刮卡效果 真的就只是刮刮卡么》
点击打开链接 该博客详细介绍了其实现步骤,现将其代码贴出(修改了少许部分):
GuaGuaKaText.java :
package com.crazy.guaguaka;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Paint.Style;
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.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
public class GuaGuaKaText extends View {
/**
* 绘制线条的Paint,即用户手指绘制Path
*/
private Paint mOutterPaint = new Paint();
/**
* 记录用户绘制的Path
*/
private Path mPath = new Path();
/**
* 内存中创建的Canvas
*/
private Canvas mCanvas;
/**
* mCanvas绘制内容在其上
*/
private Bitmap mBitmap;
/**
* ------------------------以下是奖区的一些变量
*/
private boolean isComplete;
private Paint mBackPint = new Paint();
private Rect mTextBound = new Rect();
private String mText = "¥10,000,000";
private int mLastX;
private int mLastY;
public GuaGuaKaText(Context context) {
this(context, null);
}
public GuaGuaKaText(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GuaGuaKaText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mPath = new Path();
setUpOutPaint();
setUpBackPaint();
}
/**
* 初始化canvas的绘制用的画笔
*/
private void setUpBackPaint() {
mBackPint.setStyle(Style.FILL);
mBackPint.setTextScaleX(2f);
mBackPint.setColor(Color.DKGRAY);
mBackPint.setTextSize(32);
mBackPint.getTextBounds(mText, 0, mText.length(), mTextBound);
}
@Override
protected void onDraw(Canvas canvas) {
// 绘制奖项
canvas.drawText(mText, getWidth() / 2 - mTextBound.width() / 2,
getHeight() / 2 + mTextBound.height() / 2, mBackPint);
if (!isComplete) {
drawPath();
canvas.drawBitmap(mBitmap, 0, 0, null);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
// 初始化bitmap
mBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
mOutterPaint.setStyle(Paint.Style.FILL);
mCanvas.drawRoundRect(new RectF(0, 0, width, height), 30, 30,
mOutterPaint);
mCanvas.drawBitmap(BitmapFactory.decodeResource(getResources(),
R.drawable.s_title), null, new RectF(0, 0, width, height), null);
}
/**
* 设置画笔的一些参数
*/
private void setUpOutPaint() {
// 设置画笔
mOutterPaint.setAlpha(0);
mOutterPaint.setColor(Color.parseColor("#c0c0c0"));
mOutterPaint.setAntiAlias(true);
mOutterPaint.setDither(true);
mOutterPaint.setStyle(Paint.Style.STROKE);
mOutterPaint.setStrokeJoin(Paint.Join.ROUND);
mOutterPaint.setStrokeCap(Paint.Cap.ROUND);
// 设置画笔宽度
mOutterPaint.setStrokeWidth(50);
}
/**
* 绘制线条
*/
private void drawPath() {
mOutterPaint.setStyle(Paint.Style.STROKE);
mOutterPaint
.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
mCanvas.drawPath(mPath, mOutterPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
int x = (int) event.getX();
int y = (int) event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
mPath.moveTo(mLastX, mLastY);
break;
case MotionEvent.ACTION_MOVE:
int dx = Math.abs(x - mLastX);
int dy = Math.abs(y - mLastY);
if (dx > 3 || dy > 3)
mPath.lineTo(x, y);
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_UP:
new Thread(mRunnable).start();
break;
}
invalidate();
return true;
}
/**
* 统计擦除区域任务
*/
private Runnable mRunnable = new Runnable() {
private int[] mPixels;
@Override
public void run() {
int w = getWidth();
int h = getHeight();
float wipeArea = 0;
float totalArea = w * h;
Bitmap bitmap = mBitmap;
mPixels = new int[w * h];
/**
* 拿到所有的像素信息
*/
bitmap.getPixels(mPixels, 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;
if (mPixels[index] == 0)
{
wipeArea++;
}
}
}
/**
* 根据所占百分比,进行一些操作
*/
if (wipeArea > 0 && totalArea > 0) {
int percent = (int) (wipeArea * 100 / totalArea);
Log.e("TAG", percent + "");
if (percent > 70) {
Log.e("TAG", "清除区域达到70%,下面自动清除");
isComplete = true;
postInvalidate();
}
}
}
};
}
布局文件:
activity_main.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.crazy.guaguaka.MainActivity">
<com.crazy.guaguaka.GuaGuaKa
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.crazy.guaguaka.GuaGuaKaText
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>