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

相关文章

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

MySQL双主搭建+keepalived高可用的实现

《MySQL双主搭建+keepalived高可用的实现》本文主要介绍了MySQL双主搭建+keepalived高可用的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、测试环境准备二、主从搭建1.创建复制用户2.创建复制关系3.开启复制,确认复制是否成功4.同

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定

Java实现时间与字符串互相转换详解

《Java实现时间与字符串互相转换详解》这篇文章主要为大家详细介绍了Java中实现时间与字符串互相转换的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、日期格式化为字符串(一)使用预定义格式(二)自定义格式二、字符串解析为日期(一)解析ISO格式字符串(二)解析自定义