Android 混合模式 —— PorterDuffXferMode

文章摘要

混合是模式是Android自定义View中的重要的一个环节,也是最难掌握的一个特性。本文将分以下三个内容来介绍:第一章简要介绍用处,第二章介绍简单的代码实现,第三章将详细介绍各个模式的作用,第四章将介绍使用过程中的一些注意点(坑点)。

第一章 例子

## 以前吃饭的时候,一个同事拿出了刚买的华为荣耀手机,指着上面的时钟说,彬帅,你看这动效不错吧。然后问我有什么思路,当时我看了一下,说了一种最简单的方案——使用蒙版。
Android 混合模式 —— PorterDuffXferMode_第1张图片
图1 华为荣耀时钟截图

这个动效最核心的就是刻度的起伏,我给出的方案可以用图2简单的解释。

Android 混合模式 —— PorterDuffXferMode_第2张图片
图2. 华为手机动效实现示意图
最终的实现结果如下:
图3. 最终的实现结果图

详情可以前往这里:https://github.com/Ajian-studio/GAHonorClock 查看,此外还有其他两个实现方案。

第二章 代码实现

上面的为例,怎么用代码实现蒙版的效果?

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 保存图层
        int layerCount = canvas.saveLayer(mClockViewRectF, mPaint, Canvas.ALL_SAVE_FLAG);
        mPaint.setColor(mClockScaleLineColor);

        // 绘制等长度的时钟刻度线
        float clockScaleLineStartY = mAdjustClockScaleLineStartX + mClockViewRectF.top;
        float clockScaleLineEndY = clockScaleLineStartY + mClockScaleLineHeight;
        for (int i = 0; i < DEFAULT_TOTAL_CLOCK_SCALE_LINE_NUM; i++) {
            canvas.drawLine(mClockViewCenterX, clockScaleLineStartY,
                    mClockViewCenterX, clockScaleLineEndY, mPaint);
            canvas.rotate(ANGLE_PER_SCALE, mClockViewCenterX, mClockViewCenterY);
        }

        // 设置混合模式
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));

        // 旋转蒙版,实现移动的效果
        canvas.rotate(mNowClockAngle, mClockViewCenterX, mClockViewCenterY);
        // 绘制蒙版,切除外边缘和蒙版重叠的部分刻度线
        canvas.drawBitmap(mClockMaskBitmap, null, mClockViewRectF, mPaint);
        // 关闭混合模式
        mPaint.setXfermode(null);

        // 其他无关操作
        mPaint.setColor(mClockPointColor);
        canvas.drawCircle(mClockPointCenterX, mClockPointCenterY, mClockPointRadius, mPaint);

        // 恢复图层
        canvas.restoreToCount(layerCount);

        updateTimeText(canvas);
    }
将上面的代码提炼一下,混合模式的实现简化代码如下:
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 保存图层
        int layerCount = canvas.saveLayer(mClockViewRectF, mPaint, Canvas.ALL_SAVE_FLAG);
        // 绘制 DST 图像
        canvas draw dst img
        // 设置混合模式
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
        // 旋转蒙版,实现移动的效果
        canvas.rotate(mNowClockAngle, mClockViewCenterX, mClockViewCenterY);
        // 绘制 SRC 图像
        canvas draw src img
        // 关闭混合模式
        mPaint.setXfermode(null);
        // 恢复图层
        canvas.restoreToCount(layerCount);
    }

注: 混合模式中,DST 和 SRC 区别如下:
DST,是指开启混合模式之前的绘制内容, 也就是先前的内容;
SRC, 是指当开启混合模式之后绘制的内容,也就是后绘制的内容。

第三章 混合模式介绍

在Android的源码 android.graphics.PorterDuff.java 中如下的源码:

public enum Mode {
    /** [0, 0] */
    CLEAR       (0),
    /** [Sa, Sc] */
    SRC         (1),
    /** [Da, Dc] */
    DST         (2),
    /** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */
    SRC_OVER    (3),
    /** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */
    DST_OVER    (4),
    /** [Sa * Da, Sc * Da] */
    SRC_IN      (5),
    /** [Sa * Da, Sa * Dc] */
    DST_IN      (6),
    /** [Sa * (1 - Da), Sc * (1 - Da)] */
    SRC_OUT     (7),
    /** [Da * (1 - Sa), Dc * (1 - Sa)] */
    DST_OUT     (8),
    /** [Da, Sc * Da + (1 - Sa) * Dc] */
    SRC_ATOP    (9),
    /** [Sa, Sa * Dc + Sc * (1 - Da)] */
    DST_ATOP    (10),
    /** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */
    XOR         (11),
    /** [Sa + Da - Sa*Da,
         Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */
    DARKEN      (16),
    /** [Sa + Da - Sa*Da,
         Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
    LIGHTEN     (17),
    /** [Sa * Da, Sc * Dc] */
    MULTIPLY    (13),
    /** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
    SCREEN      (14),
    /** Saturate(S + D) */
    ADD         (12),
    OVERLAY     (15);

    Mode(int nativeInt) {
        this.nativeInt = nativeInt;
    }
 }
上面的代码中的注释,解释了各个模式的最终的颜色的组成。但是看代码不直观,那么就看看下面这种 google 官方的图。
Android 混合模式 —— PorterDuffXferMode_第3张图片
图4 Google 官方Demo的各个模式的说明图

第四章 混合模式实际的坑点

大致坑点有两个:
1)硬件加速在个别机型上会影响最终的结果
2) 谷歌官方Demo的图有一定的误导

下面将就 谷歌官方Demo 做一个说明。官方的图的颜色比较淡,为了更好的说明,将替换更鲜艳的颜色,如下图:

Android 混合模式 —— PorterDuffXferMode_第4张图片
图5 使用红和蓝的Google 官方Demo的各个模式的说明图

但是我们直接试验的会得到如下的图6中右边,而得不到图4或图5的样子。

Android 混合模式 —— PorterDuffXferMode_第5张图片
图6 左图为官方demo图, 右图为实际的效果图

为什么会出现这样的偏差呢?这个问题不是我一个会遇到吧?应该会有很多人遇到。

Android 混合模式 —— PorterDuffXferMode_第6张图片
图7 左图为官方demo图的SRC, 右图为实际的效果图的SRC

通过上图7,左边为官方demo的绘制模式,虽然可见区域仅仅是蓝色方块,但是实际绘制的范围为红色框框出来的;而右图是我们实际操作过程中出现的,绘制范围仅仅是蓝色方块的范围。
这里我们得出一个结论:
混合模式发生作用的范围是,实际绘制的区域(不管相应的区域是否为可见),而不在绘制区域将不会受到任何影响。

此外现在都是讲的是不透明的情况下,那么透明情况如何?请看下图。

Android 混合模式 —— PorterDuffXferMode_第7张图片
图9 带透明度谷歌官方demo图
Android 混合模式 —— PorterDuffXferMode_第8张图片
图10 带透的绘制区域就是相应颜色区域的图

总结

到此为止,本文已经结束了。本文的示例代码可以在这里 https://github.com/linxuebin1990/TestMixedMode查看。

你可能感兴趣的:(android)