文章目录
概述
类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);
}
}
效果图如下
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本身屏幕的背景时黑色的,所以此处就显示了一个黑色的圆形。
在这里插入图片描述
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);
}
目标像素是蓝色 , 源像素是红色,进行叠加 混合像素是粉红色。
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);
}
效果图
扩展,既然 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);
}
因为只保留透明色,activity 是黑色的,所以最终的颜色值是 黑色
PorterDuff.Mode.SRC_OVER
默认的 Android 构图方式是 PorterDuff.Mode.SRC_OVER,相当于在目标图像上绘制源图像/颜色。
这种模式下,绘制的图像会覆盖目标像素上。
@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);
}
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);
}
扩展,既然 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);
}
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);
}
}
添加 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);
}
效果
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);
}
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);
}
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);
}
分析:相交的地方,放弃黄色、放弃红色,显示透明,所以看到的是蓝色背景。不相交的地方,绘制红色源像素。
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);
}
因为丢弃了红色矩形,所以显示出来的是黄色的圆形
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);
}
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);
}
实战1-蓄水池效果
先看效果图:
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-美女小猫
先看效果图
首先准备两张图片,一个小猫,一个美女
代码如下
@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