Android Canvas 绘图之 PorterDuffXfermode。
类 Android.graphics.PorterDuffXfermode 继承自 android.graphics.Xfermode。
当绘图时和前面的图案有重叠部分,为了设置重叠规则,需要用到 PorterDuffXfermode,需要将将其作为参数传给 Paint.setXfermode(Xfermode xfermode) 方法。
PorterDuffXfermode 这个类中的 Porter 和 Duff 是两个人名,这两个人在 1984 年一起写了一篇名为《Compositing Digital Images》 的论文。
下面开始介绍 PorterDuffXfermode 的使用。
示例一
在演示 Xfermode 之前,先看一下下面的例子
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int r = canvas.getWidth() / 3;
// 设置背景色
canvas.drawARGB(255, 139, 197, 186);
//绘制黄色的圆形
paint.setColor(0xFFFFCC44);
canvas.drawCircle(r, r, r, paint);
//绘制蓝色的矩形
paint.setColor(0xFF66AAFF);
canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
}
重写了 View 的 onDraw 方法,首先将 View 的背景色设置为绿色,然后绘制了一个黄色的圆形,然后再绘制一个蓝色的矩形,效果如下所示:
没有设置 Xfermode 的时候,后来绘制的图形就会覆盖之前绘制的图形!
示例二
下面我们使用 Xfermode 对上面的代码进行一下修改,修改后的代码如下所示:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int r = canvas.getWidth() / 3;
// 设置背景色
canvas.drawARGB(255, 139, 197, 186);
//绘制黄色的圆形
paint.setColor(0xFFFFCC44);
canvas.drawCircle(r, r, r, paint);
//绘制蓝色的矩形
paint.setColor(0xFF66AAFF);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
}
效果如下:
先来讲一下 setXfermode 的绘制原理:在 canvas.drawRect() 之前 setXfermode,那么所绘制的矩形中的像素称作源像素(source,简称 src),所绘制的矩形在 Canvas 中对应位置的矩形内的像素称作目标像素(destination,简称 dst)。根据 Xfermode 的规则,ARGB 的值会重新计算。
本例中 Xfermode 是 PorterDuff.Mode.CLEAR,直接将目标像素的 ARGB 四个分量全置为 0,即 (0,0,0,0),即透明色,所以实际上绘制了一个透明的矩形。但是效果图为什么是白色的呢,因为屏幕本身是白色的。
示例三
示例二中显示的白色,如果想要显示透明色,我们需要将代码作如下修改:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int r = canvas.getWidth() / 3;
// 设置背景色
canvas.drawARGB(255, 139, 197, 186);
int layerId = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);
//绘制黄色的圆形
paint.setColor(0xFFFFCC44);
canvas.drawCircle(r, r, r, paint);
//绘制蓝色的矩形
paint.setColor(0xFF66AAFF);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
canvas.restoreToCount(layerId);
}
效果如下:
我们将绘制圆形与矩形的代码放到了 canvas.saveLayer() 和 canvas.restoreToCount() 之间。
关于 canvas 绘图中的 layer 有以下几点需要说明:
Canvas 是支持图层 layer 渲染这种技术的,Canvas 默认就有一个 layer,当我们平时调用 Canvas 的各种
drawXXX() 方法时,其实是把所有的东西都绘制到 Canvas 这个默认的 layer 上面。
我们还可以通过 canvas.saveLayer() 新建一个 layer,新建的 layer 放置在 canvas 默认 layer 的上部,当我们执行了 canvas.saveLayer() 之后,我们所有的绘制操作都绘制到了我们新建的 layer 上,而不是 canvas
默认的 layer。
用 canvas.saveLayer() 方法产生的 layer 所有像素的 ARGB 值都是 (0,0,0,0),即 canvas.saveLayer()
方法产生的 layer 初始时时完全透明的。
canvas.saveLayer() 方法会返回一个 int 值,用于表示 layer 的 ID,在我们对这个新 layer 绘制完成后可以通过调用 canvas.restoreToCount(layer) 或者 canvas.restore() 把这个 layer 绘制到 canvas 默认的 layer 上去,这样就完成了一个 layer 的绘制工作。
所以 CLEAR 操作时在新建的图层上面,背景色不再是 Activity 的背景色,变成了默认图层的背景色了。
一张被不经大脑疯传的神图
如果大家 Google 或百度 PorterDuffXfermode 相关的博文,大家肯定会看到下面这张神图,如下所示:
其实这张图有点问题,具体可以参照
不同 Mode 的效果
PorterDuff.Mode.CLEAR, //清除
PorterDuff.Mode.SRC, //只绘制源图
PorterDuff.Mode.DST, //只绘制目标图像
PorterDuff.Mode.SRC_OVER, //在目标图像的上方绘制源图像
PorterDuff.Mode.DST_OVER, //在源图像的上方绘制目标图像
PorterDuff.Mode.SRC_IN, //只在源图像和目标图像相交的地方绘制源图像
PorterDuff.Mode.DST_IN, //只在源图像和目标图像相交的地方绘制目标图像
PorterDuff.Mode.SRC_OUT, //只在源图像和目标图像不相交的地方绘制源图像
PorterDuff.Mode.DST_OUT, //只在源图像和目标图像不相交的地方绘制目标图像
PorterDuff.Mode.SRC_ATOP, //在源图像和目标图像相交的地方绘制源图像,在不相交的地方绘制目标图像
PorterDuff.Mode.DST_ATOP, //在源图像和目标图像相交的地方绘制目标图像,在不相交的地方绘制源图像
PorterDuff.Mode.XOR, //在源图像和目标图像重叠之外的任何地方绘制他们,而在重叠的地方不绘制任何内容
PorterDuff.Mode.DARKEN, //变暗
PorterDuff.Mode.LIGHTEN, //变亮
PorterDuff.Mode.MULTIPLY, //正片叠底
PorterDuff.Mode.SCREEN, //滤色
PorterDuff.Mode.ADD, //饱和相加
PorterDuff.Mode.OVERLAY //叠加