效果图:
源码分析
我们现在针对Xfermode源码来分析一波(Api 27):
paint.setXfermode(Xfermode xfermode);

接口里面主要为赋值操作,会将xfermode安装到paint中,设置或者清除传输模式对象(即xfermode),传输模式定义源像素(通过绘图命令生成)如何与目标像素(渲染目标的内容)进行合成,若设置为null,则会清除任何先前的传输模式。 为了方便,传递的参数也被返回。
我们可以将Xfermode称为图像混合模式
。
PorterDuffXfermode是最常见的图像混合模式,同时还有另外两个类,PixelXorXfermode和AvoidXfermode,由于这两个类已经被弃用,我们主讲PorterDuffXfermode模式。
PorterDuffXfermode中,定义了一个PorterDuff.Mode:

我们再跟进去看看PorterDuff类,由于源码注释过多,这里不全截图示例:

该类的注释为:
类名是对托马斯波特和汤姆达夫的作品的敬意,他们在1984年发表的题为“合成数字图像”的开创性论文中作了介绍。在本文中,作者描述了12个合成操作符,这些操作符管理如何计算由目标(渲染目标的内容)组成的源(要显示的图形对象)的颜色结果。
“合成数字图像”于1984年7月在计算机图形学卷18,第3期出版。
由于Porter和Duff的工作仅关注源和目的地的alpha通道的影响,原始文章中描述的12个操作员在这里被称为alpha合成模式。
为了方便起见,这个类还提供了几种混合模式,它们类似地定义了合成源和目的地的结果,但没有被限制到alpha通道。这些混合模式并未由Porter和Duff定义,但为方便起见,已包含在本课程中。
定义了一个Mode枚举类,还有两个方法分别为模式转int和int转模式,下面我们再看看Mode这个枚举类:
public enum Mode {
// these value must match their native equivalents. See SkXfermode.h
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_CLEAR.png" />
* <figcaption>Destination pixels covered by the source are cleared to 0.</figcaption>
* </p>
* <p>\(\alpha_{out} = 0\)</p>
* <p>\(C_{out} = 0\)</p>
*/
CLEAR (0),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_SRC.png" />
* <figcaption>The source pixels replace the destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src}\)</p>
* <p>\(C_{out} = C_{src}\)</p>
*/
SRC (1),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_DST.png" />
* <figcaption>The source pixels are discarded, leaving the destination intact.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{dst}\)</p>
* <p>\(C_{out} = C_{dst}\)</p>
*/
DST (2),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_SRC_OVER.png" />
* <figcaption>The source pixels are drawn over the destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src} + (1 - \alpha_{src}) * \alpha_{dst}\)</p>
* <p>\(C_{out} = C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
*/
SRC_OVER (3),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_DST_OVER.png" />
* <figcaption>The source pixels are drawn behind the destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{dst} + (1 - \alpha_{dst}) * \alpha_{src}\)</p>
* <p>\(C_{out} = C_{dst} + (1 - \alpha_{dst}) * C_{src}\)</p>
*/
DST_OVER (4),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_SRC_IN.png" />
* <figcaption>Keeps the source pixels that cover the destination pixels,
* discards the remaining source and destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
* <p>\(C_{out} = C_{src} * \alpha_{dst}\)</p>
*/
SRC_IN (5),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_DST_IN.png" />
* <figcaption>Keeps the destination pixels that cover source pixels,
* discards the remaining source and destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
* <p>\(C_{out} = C_{dst} * \alpha_{src}\)</p>
*/
DST_IN (6),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_SRC_OUT.png" />
* <figcaption>Keeps the source pixels that do not cover destination pixels.
* Discards source pixels that cover destination pixels. Discards all
* destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = (1 - \alpha_{dst}) * \alpha_{src}\)</p>
* <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src}\)</p>
*/
SRC_OUT (7),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_DST_OUT.png" />
* <figcaption>Keeps the destination pixels that are not covered by source pixels.
* Discards destination pixels that are covered by source pixels. Discards all
* source pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = (1 - \alpha_{src}) * \alpha_{dst}\)</p>
* <p>\(C_{out} = (1 - \alpha_{src}) * C_{dst}\)</p>
*/
DST_OUT (8),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_SRC_ATOP.png" />
* <figcaption>Discards the source pixels that do not cover destination pixels.
* Draws remaining source pixels over destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{dst}\)</p>
* <p>\(C_{out} = \alpha_{dst} * C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
*/
SRC_ATOP (9),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_DST_ATOP.png" />
* <figcaption>Discards the destination pixels that are not covered by source pixels.
* Draws remaining destination pixels over source pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src}\)</p>
* <p>\(C_{out} = \alpha_{src} * C_{dst} + (1 - \alpha_{dst}) * C_{src}\)</p>
*/
DST_ATOP (10),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_XOR.png" />
* <figcaption>Discards the source and destination pixels where source pixels
* cover destination pixels. Draws remaining source pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = (1 - \alpha_{dst}) * \alpha_{src} + (1 - \alpha_{src}) * \alpha_{dst}\)</p>
* <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
*/
XOR (11),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_DARKEN.png" />
* <figcaption>Retains the smallest component of the source and
* destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
* <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + min(C_{src}, C_{dst})\)</p>
*/
DARKEN (16),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_LIGHTEN.png" />
* <figcaption>Retains the largest component of the source and
* destination pixel.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
* <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + max(C_{src}, C_{dst})\)</p>
*/
LIGHTEN (17),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_MULTIPLY.png" />
* <figcaption>Multiplies the source and destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
* <p>\(C_{out} = C_{src} * C_{dst}\)</p>
*/
MULTIPLY (13),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_SCREEN.png" />
* <figcaption>Adds the source and destination pixels, then subtracts the
* source pixels multiplied by the destination.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
* <p>\(C_{out} = C_{src} + C_{dst} - C_{src} * C_{dst}\)</p>
*/
SCREEN (14),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_ADD.png" />
* <figcaption>Adds the source pixels to the destination pixels and saturates
* the result.</figcaption>
* </p>
* <p>\(\alpha_{out} = max(0, min(\alpha_{src} + \alpha_{dst}, 1))\)</p>
* <p>\(C_{out} = max(0, min(C_{src} + C_{dst}, 1))\)</p>
*/
ADD (12),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_OVERLAY.png" />
* <figcaption>Multiplies or screens the source and destination depending on the
* destination color.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
* <p>\(\begin{equation}
* C_{out} = \begin{cases} 2 * C_{src} * C_{dst} & 2 * C_{dst} \lt \alpha_{dst} \\
* \alpha_{src} * \alpha_{dst} - 2 (\alpha_{dst} - C_{src}) (\alpha_{src} - C_{dst}) & otherwise \end{cases}
* \end{equation}\)</p>
*/
OVERLAY (15);
Mode(int nativeInt) {
this.nativeInt = nativeInt;
}
/**
* @hide
*/
public final int nativeInt;
}
该类主要定义了一些常量,即十八个常量,十八种模式:

下面介绍的所有示例图都使用相同的源图像和目标图像:

下面的代码片段显示了用于生成每个图的绘图操作顺序:
Paint paint = new Paint();
canvas.drawBitmap(destinationImage, 0, 0, paint);
PorterDuff.Mode mode = // choose a mode
paint.setXfermode(new PorterDuffXfermode(mode));
canvas.drawBitmap(sourceImage, 0, 0, paint);
即先进行一个bitmap的绘制,然后设置Xfermode,再进行另一个的bitmap的绘制,这时,会根据Xfermode的混合原则,对这两个图像进行混合处理。
Alpha合成模式

混合模式
合成方程
以下每个单独的alpha合成或混合模式的文档提供了用于计算源和目标的合成结果的alpha值和颜色值的确切公式。
结果(或输出)alpha值记为αout。 结果(或输出)颜色值被标注为Cout。
又为alpha_{out}和C_{out},通过两个图像的计算得到这两个输出值就是图像混合的核心,而下面就是具体的那些计算公式:
ADD
饱和相加,对图像饱和度进行相加
CLEAR
清除图像
DARKEN
保留源像素和目标像素的最小分量,即变暗,较深的颜色覆盖较浅的颜色,若两者深浅程度相同则混合。
DST
只显示目标图像
DST_ATOP
丢弃未被源像素覆盖的目标像素,在源像素上绘制剩余的目标像素
DST_IN
保留覆盖源像素的目标像素,丢弃剩余的源像素和目标像素
DST_OUT
保留未被源像素覆盖的目标像素, 放弃源像素覆盖的目标像素,丢弃所有源像素。
DST_OVER
源像素绘制在目标像素后面
LIGHTEN
保留源像素和目标像素的最大构成,变亮,与DARKEN相反,DARKEN和LIGHTEN生成的图像结果与Android对颜色值深浅的定义有关
MULTIPLY
将源像素和目标像素相乘
OVERLAY
覆盖
SCREEN
将源像素和目标像素相加,然后减去目标像素和源像素的相乘。
SRC
只显示源图像
SRC_ATOP
丢弃没有覆盖到目标像素的源像素,在目标像素上绘制剩余的源像素
SRC_IN
保留覆盖目标像素的源像素,丢弃剩余的源像素和目标像素
SRC_OUT
保持不包含目标像素的源像素
SRC_OVER
源像素绘制在目标像素上
XOR
丢弃源像素覆盖目标像素的源像素和目标像素,绘制剩余的源像素
源码理解
在使用Paint画笔的时候,我们可以通过设置Xfermode来对两个图像像素按照一定的规则进行混合,形成新的像素,再绘制到canvas上面去。
每个像素点的颜色都是由四个分量组成,即RGBA,RGB表示的是颜色(红绿蓝),A表示的是我们Alpha值,在Xfermode中,
alpha值为αout,颜色值被标注为Cout。
我们大概可总结一下几个重要点
:
- alpha——透明度
- C——颜色值
- src——原图像
- dst——目标图像
- out——输出
混合模式分类
我们由源码可知,PorterDuff.Mode大概可以分为三类:
SRC类
优先显示源图像
- SRC
- SRC_OVER
- SRC_IN
- SRC_OUT
- SRC_ATOP
DST类
优先显示目标图像
- DST
- DST_OVER
- DST_IN
- DST_OUT
- DST_ATOP
其他类
其它的叠加效果
- CLEAR
- XOR
- DARKEN
- LIGHTEN
- MULTIPLY
- SCREEN
- ADD
- OVERLAY
Demo测试
测试环境为:Android 8.1
谷歌官方demo十六种组合效果:

官方代码
代码为:
public class Xfermodes extends GraphicsActivity {
// create a bitmap with a circle, used for the "dst" image
static Bitmap makeDst(int w, int h) {
Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setColor(0xFFFFCC44);
c.drawOval(new RectF(0, 0, w*3/4, h*3/4), p);
return bm;
}
// create a bitmap with a rect, used for the "src" image
static Bitmap makeSrc(int w, int h) {
Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setColor(0xFF66AAFF);
c.drawRect(w/3, h/3, w*19/20, h*19/20, p);
return bm;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleView(this));
}
private static class SampleView extends View {
private static final int W = 64;
private static final int H = 64;
private static final int ROW_MAX = 4; // number of samples per row
private Bitmap mSrcB;
private Bitmap mDstB;
private Shader mBG; // background checker-board pattern
private static final Xfermode[] sModes = {
new PorterDuffXfermode(PorterDuff.Mode.CLEAR),
new PorterDuffXfermode(PorterDuff.Mode.SRC),
new PorterDuffXfermode(PorterDuff.Mode.DST),
new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),
new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),
new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),
new PorterDuffXfermode(PorterDuff.Mode.DST_IN),
new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),
new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),
new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),
new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),
new PorterDuffXfermode(PorterDuff.Mode.XOR),
new PorterDuffXfermode(PorterDuff.Mode.DARKEN),
new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),
new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),
new PorterDuffXfermode(PorterDuff.Mode.SCREEN)
};
private static final String[] sLabels = {
"Clear", "Src", "Dst", "SrcOver",
"DstOver", "SrcIn", "DstIn", "SrcOut",
"DstOut", "SrcATop", "DstATop", "Xor",
"Darken", "Lighten", "Multiply", "Screen"
};
public SampleView(Context context) {
super(context);
mSrcB = makeSrc(W, H);
mDstB = makeDst(W, H);
// make a ckeckerboard pattern
Bitmap bm = Bitmap.createBitmap(new int[] { 0xFFFFFFFF, 0xFFCCCCCC,
0xFFCCCCCC, 0xFFFFFFFF }, 2, 2,
Bitmap.Config.RGB_565);
mBG = new BitmapShader(bm,
Shader.TileMode.REPEAT,
Shader.TileMode.REPEAT);
Matrix m = new Matrix();
m.setScale(6, 6);
mBG.setLocalMatrix(m);
}
@Override protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
Paint labelP = new Paint(Paint.ANTI_ALIAS_FLAG);
labelP.setTextAlign(Paint.Align.CENTER);
Paint paint = new Paint();
paint.setFilterBitmap(false);
canvas.translate(15, 35);
int x = 0;
int y = 0;
for (int i = 0; i < sModes.length; i++) {
// draw the border
paint.setStyle(Paint.Style.STROKE);
paint.setShader(null);
canvas.drawRect(x - 0.5f, y - 0.5f,
x + W + 0.5f, y + H + 0.5f, paint);
// draw the checker-board pattern
paint.setStyle(Paint.Style.FILL);
paint.setShader(mBG);
canvas.drawRect(x, y, x + W, y + H, paint);
// draw the src/dst example into our offscreen bitmap
int sc = canvas.saveLayer(x, y, x + W, y + H, null,
Canvas.MATRIX_SAVE_FLAG |
Canvas.CLIP_SAVE_FLAG |
Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
Canvas.CLIP_TO_LAYER_SAVE_FLAG);
canvas.translate(x, y);
canvas.drawBitmap(mDstB, 0, 0, paint);
paint.setXfermode(sModes[i]);
canvas.drawBitmap(mSrcB, 0, 0, paint);
paint.setXfermode(null);
canvas.restoreToCount(sc);
// draw the label
canvas.drawText(sLabels[i],
x + W/2, y - labelP.getTextSize()/2, labelP);
x += W + 10;
// wrap around when we've drawn enough for one row
if ((i % ROW_MAX) == ROW_MAX - 1) {
x = 0;
y += H + 30;
}
}
}
}
}
自定义View
我们将其封装成一个View
public class XFerModesSampleView extends View {
private static int W = 200;
private static int H = 200;
private static final int ROW_MAX = 4; // number of samples per row
private Bitmap mSrcB;
private Bitmap mDstB;
private Shader mBG; // background checker-board pattern
private static final Xfermode[] sModes = {
new PorterDuffXfermode(PorterDuff.Mode.CLEAR),
new PorterDuffXfermode(PorterDuff.Mode.SRC),
new PorterDuffXfermode(PorterDuff.Mode.DST),
new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),
new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),
new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),
new PorterDuffXfermode(PorterDuff.Mode.DST_IN),
new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),
new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),
new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),
new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),
new PorterDuffXfermode(PorterDuff.Mode.XOR),
new PorterDuffXfermode(PorterDuff.Mode.DARKEN),
new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),
new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),
new PorterDuffXfermode(PorterDuff.Mode.SCREEN)
};
private static final String[] sLabels = {
"Clear", "Src", "Dst", "SrcOver",
"DstOver", "SrcIn", "DstIn", "SrcOut",
"DstOut", "SrcATop", "DstATop", "Xor",
"Darken", "Lighten", "Multiply", "Screen"
};
public XFerModesSampleView(Context context) {
super(context);
init();
}
private void init() {
if(Build.VERSION.SDK_INT >= 11){
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
mSrcB = makeSrc(W, H);
mDstB = makeDst(W, H);
// make a ckeckerboard pattern
Bitmap bm = Bitmap.createBitmap(new int[] { 0xFFFFFFFF, 0xFFCCCCCC,
0xFFCCCCCC, 0xFFFFFFFF }, 2, 2,
Bitmap.Config.RGB_565);
mBG = new BitmapShader(bm,
Shader.TileMode.REPEAT,
Shader.TileMode.REPEAT);
Matrix m = new Matrix();
m.setScale(6, 6);
mBG.setLocalMatrix(m);
}
@Override protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
Paint labelP = new Paint(Paint.ANTI_ALIAS_FLAG);
labelP.setTextAlign(Paint.Align.CENTER);
Paint paint = new Paint();
paint.setFilterBitmap(false);
canvas.translate(15, 35);
int x = 0;
int y = 0;
for (int i = 0; i < sModes.length; i++) {
// draw the border
paint.setStyle(Paint.Style.STROKE);
paint.setShader(null);
canvas.drawRect(x - 0.5f, y - 0.5f,
x + W + 0.5f, y + H + 0.5f, paint);
// draw the checker-board pattern
paint.setStyle(Paint.Style.FILL);
paint.setShader(mBG);
canvas.drawRect(x, y, x + W, y + H, paint);
// draw the src/dst example into our offscreen bitmap
int sc = canvas.saveLayer(x, y, x + W, y + H, null,Canvas.ALL_SAVE_FLAG);
canvas.translate(x, y);
canvas.drawBitmap(mDstB, 0, 0, paint);
paint.setXfermode(sModes[i]);
canvas.drawBitmap(mSrcB, 0, 0, paint);
paint.setXfermode(null);
canvas.restoreToCount(sc);
// draw the label
canvas.drawText(sLabels[i],
x + W/2, y - labelP.getTextSize()/2, labelP);
x += W + 10;
// wrap around when we've drawn enough for one row
if ((i % ROW_MAX) == ROW_MAX - 1) {
x = 0;
y += H + 30;
}
}
}
// create a bitmap with a circle, used for the "dst" image
static Bitmap makeDst(int w, int h) {
Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setColor(0xFFFFCC44);
c.drawOval(new RectF(0, 0, w*3/4, h*3/4), p);
return bm;
}
// create a bitmap with a rect, used for the "src" image
static Bitmap makeSrc(int w, int h) {
Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setColor(0xFF66AAFF);
c.drawRect(w/3, h/3, w*19/20, h*19/20, p);
return bm;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
H = W = (int) (w / 4.5f);
}
}
与官方Demo的差异
我们可以看到增加了下面代码:
if(Build.VERSION.SDK_INT >= 11){
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
若不增加这句代码,出现的结果为:

而增加这句代码,出现的结果为:

可以看到不增加上面代码的话,Clear,Darken,Lighten这三种与官方描述的不一致,而增加上面代码的话,与官方描述是一样的,那么这是什么原因引起的呢?
setLayerType作用:
指定支持此视图的图层的类型。 该图层可以是LAYER_TYPE_NONE,LAYER_TYPE_SOFTWARE或LAYER_TYPE_HARDWARE。
一个图层与一个可选的Paint实例相关联,该实例控制图层在屏幕上的组成方式。 组成图层时考虑以下涂料属性:
半透明(alpha)
混合模式
彩色滤光片
如果通过调用setAlpha(float)将此视图的alpha值设置为<1.0,则该图层的alpha值将被此视图的alpha值所取代。
LAYER_TYPE_SOFTWARE作用:
表示该视图具有软件层。一个软件层由一个bitmap支持,并使视图使用Android的软件渲染管道渲染,即使启用了硬件加速。
软件层有各种用途:
当应用程序未使用硬件加速时,软件层对于将特定颜色过滤器和/或混合模式和/或半透明应用于视图及其所有子项很有用。
当应用程序使用硬件加速时,软件层可用于渲染硬件加速管道不支持的绘制原语。它也可用于将复杂的视图树缓存到纹理中,并降低绘制操作的复杂性。例如,当使用翻译对复杂视图树进行动画处理时,可以使用软件层仅渲染一次视图树。
受影响的视图树经常更新时应避免软件层。每次更新都需要重新渲染软件层,这可能会很慢(特别是当硬件加速打开时,因为在每次更新后必须将图层上载到硬件纹理中)。
即调用这句代码意思为对混合模式起作用;
要注意的地方
注意,我们要想调用Xfermode生效,须按照以下顺序进行draw:

即先画目标bitmap,在调用setXfermode,再画源bitmap,若调用位置相反,则会显示相反的效果。
刮刮卡效果实现
我们知道了Xfermode的作用,那么我们可以运用一下,来实现一个刮刮卡的效果,我们将

设置为底图,紧接着我们创建一个图层,在这个图层上画了一个新建的bitmap,并将其作为目标图,若手指移动的话,则在目标图上绘制在目标图上进行手指轨迹的移动,因为我们新建的bitmap为透明底,因此手指轨迹实为图像的内容,根据PorterDuff.Mode.SRC_OUT
的特性,会将src和dst图像相交的像素点过滤且显示src图像,这时会漏出底图即”恭喜你中了一等奖”的图像,因此实现了刮刮卡的效果。
完整代码
public class ScratchCardView extends View {
private Paint paint;
private Bitmap dstBitmap, srcBitmap, realRewardBitmap;
private Path path;
private float touchX, touchY;
public ScratchCardView(Context context) {
super(context);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(40);
//真实的奖励图
realRewardBitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.scratch_2,null);
//原图,即要刮走的图
srcBitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.scratch_1,null);
//目标图
dstBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.ARGB_8888);
path = new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(realRewardBitmap,0,0, paint); //画底图
int count = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); //创建画布
new Canvas(dstBitmap).drawPath(path, paint); //新建一个canvas,将手指轨迹画到目标Bitmap上
canvas.drawBitmap(dstBitmap,0,0, paint);//将目标图像画到画布上
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)); //设置图像混合模式
canvas.drawBitmap(srcBitmap,0,0,paint); //将最上面的刮刮奖图片画到画布上
paint.setXfermode(null); //清空Xfermode
canvas.restoreToCount(count);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
path.moveTo(event.getX(),event.getY());
touchX = event.getX();
touchY = event.getY();
return true;
case MotionEvent.ACTION_MOVE:
float endX = (touchX + event.getX()) / 2;
float endY = (touchY + event.getY()) / 2;
path.quadTo(touchX,touchY,endX,endY);
touchX = event.getX();
touchY = event.getY();
break;
case MotionEvent.ACTION_UP:
break;
}
invalidate();
return super.onTouchEvent(event);
}
}
效果图:
Demo地址
Demo地址:https://github.com/samlss/PaintXfermode
个人总结:https://github.com/samlss/AsAndroidDevelop