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

效果图:
这里写图片描述

源码分析

我们现在针对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 mode
 paint.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" 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

猜你喜欢

转载自blog.csdn.net/samlss/article/details/80798023

相关文章