文章目录

概述

类​​android.graphics.PorterDuffXfermode​​​继承自​​android.graphics.Xfermode​​​。在用Android中的Canvas进行绘图时,可以通过使用​​PorterDuffXfermode​​​将所绘制的图形的像素与​​Canvas​​​中对应位置的像素按照一定规则进行混合,形成新的像素值,从而更新​​Canvas​​​中最终的像素颜色值,这样会创建很多有趣的效果。当使用​​PorterDuffXfermode​​​时,需要将将其作为参数传给​​Paint.setXfermode(Xfermode xfermode)​​​方法,这样在用该画笔​​paint​​​进行绘图时,Android就会使用传入的​​PorterDuffXfermode​​​,如果不想再使用​​Xfermode​​​,那么可以执行​​Paint.setXfermode(null)​​。

​PorterDuffXfermode​​​ 这个类中的 ​​Porter​​​ 和 ​​Duff​​ 是两个人名,这两个人在1984年一起写了一篇名为《Compositing Digital Images》的论文,点击可查看该论文。我们知道,一个像素是由RGBA四个分量组成的,该论文就论述了如何实现不同数字图像的像素之间是如何进行混合的,该论文提出了多种像素混合的模式。如果做过图像处理开发,会对其比较了解,该技术也和OpenGL中的Alpha混合技术异曲同工。

​PorterDuffXfermode​​支持以下十几种像素颜色的混合模式,分别为:CLEAR、SRC、DST、SRC_OVER、DST_OVER、SRC_IN、DST_IN、SRC_OUT、DST_OUT、SRC_ATOP、DST_ATOP、XOR、DARKEN、LIGHTEN、MULTIPLY、SCREEN。

我们下面会分析几个代码片段研究​​PorterDuffXfermode​​使用及工作原理详解。

官方文字:​​https://developer.android.com/reference/android/graphics/PorterDuff.Mode​

PorterDuffXfermode 原理

Android 使用 Canvas 在 View 上绘制图形 ,所绘制的图形中的像素称作源像素(source,简称src),所绘制的矩形在Canvas中对应位置的矩形内的像素称作目标像素(destination,简称dst)。源像素的ARGB四个分量会和Canvas上同一位置处的目标像素的ARGB四个分量按照 ​​Xfermode​​​定义的规则进行计算,形成最终的​​ARGB​​值,然后用该最终的ARGB值更新目标像素的ARGB值。

准备

准备自定义 MyView , 并且设置背景色为 蓝色

<com.zyj.demo.MyView
android:background="#FF3700B3"
android:layout_gravity="center"
android:layout_width="300dp"
android:layout_height="300dp" />

MyView

public class MyView extends View {

private Paint mPaint;

public MyView(Context context) {
super(context);
initPaint();
}

public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initPaint();
}

public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint();
}

private void initPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

mPaint.setColor(Color.RED);
canvas.drawCircle(centerX, centerY, radius, mPaint);
}
}

效果图如下

Android PorterDuffXfermode使用及工作原理详解_ide

PorterDuff.Mode.CLEAR

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

mPaint.setColor(Color.RED);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawCircle(centerX, centerY, radius, mPaint);
mPaint.setXfermode(null);
}

该规则比较简单粗暴,直接要求目标像素的ARGB四个分量全置为0,即(0,0,0,0),即透明色。

由于Activity本身屏幕的背景时黑色的,所以此处就显示了一个黑色的圆形。

在这里插入图片描述

Android PorterDuffXfermode使用及工作原理详解_颜色值_02

PorterDuff.Mode.ADD

将源像素 和 目标像素 进行叠加,生成新的像素值。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

mPaint.setColor(Color.RED);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
canvas.drawCircle(centerX, centerY, radius, mPaint);
mPaint.setXfermode(null);
}

Android PorterDuffXfermode使用及工作原理详解_android_03

目标像素是蓝色 , 源像素是红色,进行叠加 混合像素是粉红色。

PorterDuff.Mode.SRC

只保留源图像的 alpha 和 color ,所以绘制出来只有源图,上图中就是红色圆形。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

mPaint.setColor(Color.RED);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
canvas.drawCircle(centerX, centerY, radius, mPaint);
mPaint.setXfermode(null);
}

效果图

Android PorterDuffXfermode使用及工作原理详解_颜色值_04

扩展,既然 SRC 模式是保留源像素,那么我们可以绘制一个透明色,来达到 CLEAR 的效果。如下;

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

//透明色
mPaint.setColor(Color.TRANSPARENT);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
canvas.drawCircle(centerX, centerY, radius, mPaint);
mPaint.setXfermode(null);
}

Android PorterDuffXfermode使用及工作原理详解_ide_05


因为只保留透明色,activity 是黑色的,所以最终的颜色值是 黑色

PorterDuff.Mode.SRC_OVER

默认的 Android 构图方式是 PorterDuff.Mode.SRC_OVER,相当于在目标图像上绘制源图像/颜色。

Android PorterDuffXfermode使用及工作原理详解_android studio_06


这种模式下,绘制的图像会覆盖目标像素上。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

mPaint.setColor(Color.RED);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
canvas.drawCircle(centerX, centerY, radius, mPaint);
mPaint.setXfermode(null);
}

Android PorterDuffXfermode使用及工作原理详解_android studio_07

PorterDuff.Mode.SRC

只保留源图像的 alpha 和 color ,所以绘制出来只有源图,上图中就是红色圆形。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

//绘制空心圆
mPaint.setColor(Color.YELLOW);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(30);
canvas.drawCircle(centerX, centerY, radius, mPaint);

//绘制矩形
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
canvas.drawRect(rect, mPaint);

mPaint.setXfermode(null);
canvas.restoreToCount(id);
}

Android PorterDuffXfermode使用及工作原理详解_颜色值_08

扩展,既然 SRC 模式是保留源像素,那么我们可以绘制一个透明色,来达到 CLEAR 的效果。如下;

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

//绘制空心圆
mPaint.setColor(Color.YELLOW);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(30);
canvas.drawCircle(centerX, centerY, radius, mPaint);

//绘制矩形
mPaint.setColor(Color.TRANSPARENT);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
canvas.drawRect(rect, mPaint);

mPaint.setXfermode(null);
canvas.restoreToCount(id);
}

Android PorterDuffXfermode使用及工作原理详解_android_09

PorterDuff.Mode.SRC_IN

在两者相交的地方绘制源图像,不相交的地方,丢弃源像素。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

//绘制空心圆
mPaint.setColor(Color.YELLOW);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(30);
canvas.drawCircle(centerX, centerY, radius, mPaint);

//绘制矩形
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
canvas.drawRect(rect, mPaint);

canvas.restoreToCount(id);
}
}

Android PorterDuffXfermode使用及工作原理详解_android studio_10


添加 PorterDuff.Mode.SRC_IN 后

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

//绘制空心圆
mPaint.setColor(Color.YELLOW);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(30);
canvas.drawCircle(centerX, centerY, radius, mPaint);

//绘制矩形
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
canvas.drawRect(rect, mPaint);
mPaint.setXfermode(null);

canvas.restoreToCount(id);
}

效果

Android PorterDuffXfermode使用及工作原理详解_android_11

PorterDuff.Mode.CLEAR

添加 PorterDuff.Mode.CLEAR , 相交的地方变成透明,不相交的地方,丢弃源像素。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

//绘制空心圆
mPaint.setColor(Color.YELLOW);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(30);
canvas.drawCircle(centerX, centerY, radius, mPaint);

//绘制矩形
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
canvas.drawRect(rect, mPaint);
mPaint.setXfermode(null);

canvas.restoreToCount(id);
}

Android PorterDuffXfermode使用及工作原理详解_android_12

PorterDuff.Mode.DST_IN

相交的地方,保留目标像素,不相交的地方,丢弃源像素。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

//绘制空心圆
mPaint.setColor(Color.YELLOW);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(30);
canvas.drawCircle(centerX, centerY, radius, mPaint);

//绘制矩形
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
canvas.drawRect(rect, mPaint);
mPaint.setXfermode(null);

canvas.restoreToCount(id);
}

Android PorterDuffXfermode使用及工作原理详解_颜色值_13

PorterDuff.Mode.XOR

相交的地方,同时放弃源像素 和 目标像素,也就是说相交的地方变成透明色。不相交的地方,保留源像素。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

//绘制空心圆
mPaint.setColor(Color.YELLOW);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(30);
canvas.drawCircle(centerX, centerY, radius, mPaint);

//绘制矩形
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
canvas.drawRect(rect, mPaint);
mPaint.setXfermode(null);

canvas.restoreToCount(id);
}

Android PorterDuffXfermode使用及工作原理详解_颜色值_14


分析:相交的地方,放弃黄色、放弃红色,显示透明,所以看到的是蓝色背景。不相交的地方,绘制红色源像素。

PorterDuff.Mode.DST

丢弃所有源像素,而目标像素保持不变

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

//绘制空心圆
mPaint.setColor(Color.YELLOW);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(30);
canvas.drawCircle(centerX, centerY, radius, mPaint);

//绘制矩形
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST));
Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
canvas.drawRect(rect, mPaint);

mPaint.setXfermode(null);
canvas.restoreToCount(id);
}

Android PorterDuffXfermode使用及工作原理详解_android studio_15


因为丢弃了红色矩形,所以显示出来的是黄色的圆形

PorterDuff.Mode.DST_IN

相交的地方,保留目标像素,丢弃所有源像素。

我在测试的时候,发现 ​​DST_IN​​​ 显示的效果和 ​​DST​​ 一样

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

//绘制空心圆
mPaint.setColor(Color.YELLOW);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(30);
canvas.drawCircle(centerX, centerY, radius, mPaint);

//绘制矩形
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
canvas.drawRect(rect, mPaint);

mPaint.setXfermode(null);
canvas.restoreToCount(id);
}

Android PorterDuffXfermode使用及工作原理详解_ide_16

PorterDuff.Mode.DST_OUT

相交的地方,同时丢弃源像素和目标像素,即显示透明。未相交的地方,丢弃源像素,保留目标像素

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

//绘制空心圆
mPaint.setColor(Color.YELLOW);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(30);
canvas.drawCircle(centerX, centerY, radius, mPaint);

//绘制矩形
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
canvas.drawRect(rect, mPaint);

mPaint.setXfermode(null);
canvas.restoreToCount(id);
}

Android PorterDuffXfermode使用及工作原理详解_ide_17

实战1-蓄水池效果

先看效果图:

Android PorterDuffXfermode使用及工作原理详解_android studio_18

public class MyView extends View {

private Paint mPaint;

public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initPaint();
}

private void initPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
}

int top = -1;

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = getWidth() / 3;

int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

//绘制空心圆
mPaint.setColor(Color.YELLOW);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(30);
canvas.drawCircle(centerX, centerY, radius, mPaint);

//绘制矩形
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
if (top == -1) {
top = getHeight();
} else {
top = top - 2;
if (top <= 0) {
top = 0;
}
}
Rect rect = new Rect(0, top, getWidth(), getHeight());
canvas.drawRect(rect, mPaint);

mPaint.setXfermode(null);
canvas.restoreToCount(id);

if (top > 0) {
invalidate();
}
}

实战2-美女小猫

先看效果图

Android PorterDuffXfermode使用及工作原理详解_android studio_19


首先准备两张图片,一个小猫,一个美女

Android PorterDuffXfermode使用及工作原理详解_android studio_20


Android PorterDuffXfermode使用及工作原理详解_android studio_21


代码如下

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;

int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

// 绘制猫
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.cat);
bitmap = Bitmap.createScaledBitmap(bitmap, centerX, centerY, true);
canvas.drawBitmap(bitmap, centerX - bitmap.getWidth() / 2, centerY - bitmap.getHeight() / 2, null);

//绘制美女
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
Bitmap starBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.meilv);
starBitmap = Bitmap.createScaledBitmap(starBitmap, centerX, centerY, true);
canvas.drawBitmap(starBitmap, centerX - starBitmap.getWidth() / 2, centerY - starBitmap.getHeight() / 2, mPaint);

mPaint.setXfermode(null);

canvas.restoreToCount(id);
}

这种效果我们用了 PorterDuff.Mode.SRC_IN 。特性是:相交的部分,绘制源像素,不相交的地方,丢弃源像素。

实战3-新手引导挖孔Dialog

Android PorterDuffXfermode使用及工作原理详解_颜色值_22