Android Paint系列之Xfermode + 刮刮卡效果实现

2023-10-28 14:38

本文主要是介绍Android Paint系列之Xfermode + 刮刮卡效果实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

效果图:
这里写图片描述

  • 源码分析
    • Alpha合成模式
    • 混合模式
    • 合成方程
      • ADD
      • CLEAR
      • DARKEN
      • DST
      • DST_ATOP
      • DST_IN
      • DST_OUT
      • DST_OVER
      • LIGHTEN
      • MULTIPLY
      • OVERLAY
      • SCREEN
      • SRC
      • SRC_ATOP
      • SRC_IN
      • SRC_OUT
      • SRC_OVER
      • XOR
  • 源码理解
    • 混合模式分类
      • SRC类
      • DST类
      • 其他类
  • Demo测试
    • 官方代码
    • 自定义View
    • 与官方Demo的差异
    • 要注意的地方
  • 刮刮卡效果实现
  • Demo地址

源码分析

我们现在针对Xfermode源码来分析一波(Api 27):


paint.setXfermode(Xfermode xfermode);


api 27 paint源码

接口里面主要为赋值操作,会将xfermode安装到paint中,设置或者清除传输模式对象(即xfermode),传输模式定义源像素(通过绘图命令生成)如何与目标像素(渲染目标的内容)进行合成,若设置为null,则会清除任何先前的传输模式。 为了方便,传递的参数也被返回。

我们可以将Xfermode称为图像混合模式

PorterDuffXfermode是最常见的图像混合模式,同时还有另外两个类,PixelXorXfermode和AvoidXfermode,由于这两个类已经被弃用,我们主讲PorterDuffXfermode模式。

PorterDuffXfermode中,定义了一个PorterDuff.Mode:


api 27 PorterDuffXfermode源码

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


api 27 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;}

该类主要定义了一些常量,即十八个常量,十八种模式:


api 27 PorterDuff源码

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


api 27 api 参考

下面的代码片段显示了用于生成每个图的绘图操作顺序:

 Paint paint = new Paint();canvas.drawBitmap(destinationImage, 0, 0, paint);PorterDuff.Mode mode = // choose a modepaint.setXfermode(new PorterDuffXfermode(mode));canvas.drawBitmap(sourceImage, 0, 0, paint);

即先进行一个bitmap的绘制,然后设置Xfermode,再进行另一个的bitmap的绘制,这时,会根据Xfermode的混合原则,对这两个图像进行混合处理。

Alpha合成模式


api 27 PorterDuff.Mode

混合模式

api 27 PorterDuff.Mode

合成方程

以下每个单独的alpha合成或混合模式的文档提供了用于计算源和目标的合成结果的alpha值和颜色值的确切公式。

结果(或输出)alpha值记为αout。 结果(或输出)颜色值被标注为Cout。

又为alpha_{out}和C_{out},通过两个图像的计算得到这两个输出值就是图像混合的核心,而下面就是具体的那些计算公式:

ADD

饱和相加,对图像饱和度进行相加

api27 ADD

api27 ADD

CLEAR

清除图像
api 27 CLERA

api 27 CLERA

DARKEN

保留源像素和目标像素的最小分量,即变暗,较深的颜色覆盖较浅的颜色,若两者深浅程度相同则混合。
api 27 DARKEN

api 27 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十六种组合效果:


![谷歌官方demo十六种组合效果](https://img-blog.csdn.net/20180625111800246?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NhbWxzcw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

官方代码

代码为:

public class Xfermodes extends GraphicsActivity {// create a bitmap with a circle, used for the "dst" imagestatic 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" imagestatic 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;}@Overrideprotected 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 rowprivate Bitmap mSrcB;private Bitmap mDstB;private Shader mBG;     // background checker-board patternprivate 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 patternBitmap 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 borderpaint.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 patternpaint.setStyle(Paint.Style.FILL);paint.setShader(mBG);canvas.drawRect(x, y, x + W, y + H, paint);// draw the src/dst example into our offscreen bitmapint 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 labelcanvas.drawText(sLabels[i],x + W/2, y - labelP.getTextSize()/2, labelP);x += W + 10;// wrap around when we've drawn enough for one rowif ((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 rowprivate Bitmap mSrcB;private Bitmap mDstB;private Shader mBG;     // background checker-board patternprivate 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 patternBitmap 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 borderpaint.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 patternpaint.setStyle(Paint.Style.FILL);paint.setShader(mBG);canvas.drawRect(x, y, x + W, y + H, paint);// draw the src/dst example into our offscreen bitmapint 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 labelcanvas.drawText(sLabels[i],x + W/2, y - labelP.getTextSize()/2, labelP);x += W + 10;// wrap around when we've drawn enough for one rowif ((i % ROW_MAX) == ROW_MAX - 1) {x = 0;y += H + 30;}}}// create a bitmap with a circle, used for the "dst" imagestatic 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" imagestatic 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;}@Overrideprotected 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();}@Overrideprotected 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); //清空Xfermodecanvas.restoreToCount(count);}@Overridepublic 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

这篇关于Android Paint系列之Xfermode + 刮刮卡效果实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/293886

相关文章

python生成随机唯一id的几种实现方法

《python生成随机唯一id的几种实现方法》在Python中生成随机唯一ID有多种方法,根据不同的需求场景可以选择最适合的方案,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习... 目录方法 1:使用 UUID 模块(推荐)方法 2:使用 Secrets 模块(安全敏感场景)方法

Spring StateMachine实现状态机使用示例详解

《SpringStateMachine实现状态机使用示例详解》本文介绍SpringStateMachine实现状态机的步骤,包括依赖导入、枚举定义、状态转移规则配置、上下文管理及服务调用示例,重点解... 目录什么是状态机使用示例什么是状态机状态机是计算机科学中的​​核心建模工具​​,用于描述对象在其生命

Spring Boot 结合 WxJava 实现文章上传微信公众号草稿箱与群发

《SpringBoot结合WxJava实现文章上传微信公众号草稿箱与群发》本文将详细介绍如何使用SpringBoot框架结合WxJava开发工具包,实现文章上传到微信公众号草稿箱以及群发功能,... 目录一、项目环境准备1.1 开发环境1.2 微信公众号准备二、Spring Boot 项目搭建2.1 创建

IntelliJ IDEA2025创建SpringBoot项目的实现步骤

《IntelliJIDEA2025创建SpringBoot项目的实现步骤》本文主要介绍了IntelliJIDEA2025创建SpringBoot项目的实现步骤,文中通过示例代码介绍的非常详细,对大家... 目录一、创建 Spring Boot 项目1. 新建项目2. 基础配置3. 选择依赖4. 生成项目5.

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期