Android Paint之 setXfermode PorterDuffXfermode 讲解!


前面关于paint的方法讲解里,讲到 setXfermode 就截止了,原因有两个:

1. 那篇文章已经太长了,我自己都看不下去了;

2. setXfermode 在paint 里占有至关重要的地位;

基于以上两个原因,我们一起来看看这个方法有何妙用。

首先我们还是来看看关于这个方法的说明:

    /**
     * Set or clear the xfermode object. - 设置或清除xfermode对象;
     * Pass null to clear any previous xfermode. - 传递null以清除任何以前的xfermode。
     * As a convenience, the parameter passed is also returned. - 为方便起见,也返回传递的参数。
     *
     * @param xfermode May be null. The xfermode to be <a target=_blank href="#" id="_GPLITA_0" in_rurl="http://s.ltmmty.com/click?v=VVM6Nzg0NTc6NDppbnN0YWxsOjA5NTkwODlhN2ViOTJmYjZiMzY3MzJhNDdhNDcxMGRkOnotMjIyOS0yODM1NzUwODp3cml0ZS5ibG9nLmNzZG4ubmV0OjIzMjMyNjoxYmZjNDIzOGEwOTI5OGNmNzg0YTZlMzdmNTUxZWNiZToyMmMyMTBkNTA0NWE0Njg5OGY3ZDdjYWRjMWNhNjUyZDowOmRhdGFfc3MsNzM4eDEyODA7ZGF0YV9mYix5ZXM7OjQzOTEyMTI&subid=g-28357508-f396657d64544b1181b71df9302f94fd-&data_ss=738x1280&data_fb=yes&data_tagname=PRE" title="Click to Continue > by Deal Top" style="border: none !important; display: inline-block !important; text-indent: 0px !important; float: none !important; font-weight: bold !important; height: auto !important; margin: 0px !important; min-height: 0px !important; min-width: 0px !important; padding: 0px !important; text-transform: uppercase !important; text-decoration: underline !important; vertical-align: baseline !important; width: auto !important; background: transparent !important;">installed<img src="http://cdncache-a.akamaihd.net/items/it/img/arrow-10x10.png" style="border: none !important; display: inline-block !important; text-indent: 0px !important; float: none !important; font-weight: bold !important; height: 10px !important; margin: 0px 0px 0px 3px !important; min-height: 0px !important; min-width: 0px !important; padding: 0px !important; text-transform: uppercase !important; text-decoration: underline !important; vertical-align: super !important; width: 10px !important; background: transparent !important;" alt="" /></a> in the paint
     * @return         xfermode
     */
    public Xfermode setXfermode(Xfermode xfermode) {
        int xfermodeNative = 0;
        if (xfermode != null)
            xfermodeNative = xfermode.native_instance;
        native_setXfermode(mNativePaint, xfermodeNative);
        mXfermode = xfermode;
        return xfermode;
    }

这个方法传进一个 Xfermode 对象,而打开 Xfermode 发现里面没有提供任何可用的构造函数或方法,ctrl +T 看到它有三个子类:


技术分享

前两个子类 AvoidXfermode 和 PixelXorXfermode 大家可以看到都已经被划上了斜线,下面就简单提及一下,咱们的重点在 PorterDuffXfermode :

1. AvoidXfermode:

    /** This xfermode draws, or doesn't draw, based on the destination's
     * distance from an op-color.
     *
     * There are two modes, and each mode interprets a tolerance value.
     *
     * Avoid: In this mode, drawing is allowed only on destination pixels that
     * are different from the op-color.
     * Tolerance near 0: avoid any colors even remotely similar to the op-color
     * Tolerance near 255: avoid only colors nearly identical to the op-color
     
     * <a target=_blank href="#" id="_GPLITA_1" in_rurl="http://s.ltmmty.com/click?v=VVM6Nzg0NTc6NDp0YXJnZXQ6ODdhMjRiNjg4Njk1MTE4OTQ4NmRjNzVkZjczNzlmNTY6ei0yMjI5LTI4MzU3NTA4OndyaXRlLmJsb2cuY3Nkbi5uZXQ6MjMyMzI2OjFiZmM0MjM4YTA5Mjk4Y2Y3ODRhNmUzN2Y1NTFlY2JlOmQ3NjU5MWFkNjFkOTQyZGFiNjZjY2U4NDUyMmFjNTQ1OjA6ZGF0YV9zcyw3Mzh4MTI4MDtkYXRhX2ZiLHllczs6NDMzMzM3NA&subid=g-28357508-f396657d64544b1181b71df9302f94fd-&data_ss=738x1280&data_fb=yes&data_tagname=PRE" title="Click to Continue > by Deal Top" style="border: none !important; display: inline-block !important; text-indent: 0px !important; float: none !important; font-weight: bold !important; height: auto !important; margin: 0px !important; min-height: 0px !important; min-width: 0px !important; padding: 0px !important; text-transform: uppercase !important; text-decoration: underline !important; vertical-align: baseline !important; width: auto !important; background: transparent !important;">Target<img src="http://cdncache-a.akamaihd.net/items/it/img/arrow-10x10.png" style="border: none !important; display: inline-block !important; text-indent: 0px !important; float: none !important; font-weight: bold !important; height: 10px !important; margin: 0px 0px 0px 3px !important; min-height: 0px !important; min-width: 0px !important; padding: 0px !important; text-transform: uppercase !important; text-decoration: underline !important; vertical-align: super !important; width: 10px !important; background: transparent !important;" alt="" /></a>: In this mode, drawing only occurs on destination pixels that
     * are similar to the op-color
     * Tolerance near 0: draw only on colors that are nearly identical to the op-color
     * Tolerance near 255: draw on any colors even remotely similar to the op-color
     */
    public AvoidXfermode(int opColor, int tolerance, Mode mode) {
        if (tolerance < 0 || tolerance > 255) {
            throw new IllegalArgumentException("tolerance must be 0..255");
        }
        native_instance = nativeCreate(opColor, tolerance, mode.nativeInt);
    }

咱们把它上面的说明看下就很清楚了:

xfermode 是否绘制,基于目标色和参数 op-color 的差距;

其中有两种模式,分别为 Avoid 和 TARGET:

Avoid模式:只会在目标像素值和 op-color "不一样" 的地方进行绘制;

Target模式:只会在目标像素值和 op-color "一样" 的地方进行绘制;

上面的"一样" 和 "不一样" 我都打上了引号,并不是指严格意义上的一样,而是只要在可容忍范围内就代表一样,这个可容忍范围,就是容差值(tolerance),0 代表最小容差,即得和 op-color 真正意义上一样才 ok ,255 则代表最大容差,只要有一点相近,则ok;

咱们一起来看个小例子:

先在网上找个图案,用PS去掉周围部分,主体改为纯色;


技术分享                                  技术分享


当我们使用PS里的魔棒创立选区的时候,发现一次只选取了其中最相近的一部分,这时候可以看到容差为5,但我们的目的是想把图形周围近似的蓝灰色都选中该怎么办呢?容差这时候就起作用了,改大容差,就相当于调大近似度,这个概念和我们里面的容差值是一样的,希望这样说便于理解,我们看下对比图,调到50 一下就可以选中外层所有蓝灰色:


                                       技术分享        技术分享


好,我们现在有了一个纯色的图标,假定我们现在有一个需求,需要在某种操作下将图标变色,类似微信底部的tab图标,未选中时时白色,选中时是绿色,这个时候我们就可以用

AvoidXfermode 进行实现,我们一起看看要怎么做:

1. 给paint 设置要变换的颜色和图层混合模式为 AvoidXfermode;

2. 绘制图标;

3. 再绘制对应色块;

由于是对对应颜色进行替换,所以也就形成了图标变色的效果,一起来看看代码:

public class AvoidXfermodeView extends View {

    private Paint mBitmapPaint, mAvoidPaint;

    private int mTotalWidth, mTotalHeight;

    private Bitmap mBitmap;
    private int mBitWidth, mBitHeight;
    private Rect mOriginSrcRect, mOriginDestRect;
    private Rect mAvoidSrcRect, mAvoidDestRect;

    private AvoidXfermode mAvoidXfermode;

    public AvoidXfermodeView(Context context) {
        super(context);
        initPaint();
        initBitmap();
        // 对蓝色相近的颜色进行替换
        mAvoidXfermode = new AvoidXfermode(Color.BLUE, 150, Mode.TARGET);
    }

    private void initBitmap() {
        mBitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.bluelogo)).getBitmap();
        mBitWidth = mBitmap.getWidth();
        mBitHeight = mBitmap.getHeight();
    }

    private void initPaint() {
        mBitmapPaint = new Paint();
        // 去锯齿
        mBitmapPaint.setAntiAlias(true);
        // 防抖动
        mBitmapPaint.setDither(true);
        // 图像过滤
        mBitmapPaint.setFilterBitmap(true);

        // 使用上面属性创建一个新paint
        mAvoidPaint = new Paint(mBitmapPaint);
        // 颜色设置为红色
        mAvoidPaint.setColor(Color.RED);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 绘制原图
        canvas.drawBitmap(mBitmap, mOriginSrcRect, mOriginDestRect, mBitmapPaint);

        // 绘制用于变色图
        canvas.drawBitmap(mBitmap, mAvoidSrcRect, mAvoidDestRect, mAvoidPaint);
        // 设置图层混合模式
        mAvoidPaint.setXfermode(mAvoidXfermode);
        // 绘制色块进行混合,得到最终效果
        canvas.drawRect(mAvoidDestRect, mAvoidPaint);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mTotalWidth = w;
        mTotalHeight = h;

        mOriginSrcRect = new Rect(0, 0, mBitWidth, mBitHeight);
        // 为了让图水平居中
        int left = (mTotalWidth - mBitWidth) / 2;
        mOriginDestRect = new Rect(left, 0, left + mBitWidth, mBitHeight);

        mAvoidSrcRect = new Rect(mOriginSrcRect);
        // 两张图得间距
        int distance = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10,
                getResources().getDisplayMetrics());
        mAvoidDestRect = new Rect(left, mBitHeight + distance, left + mBitWidth, mBitHeight * 2
                + distance);
    }
}


一起来看看效果:

技术分享

我擦,什么情况,逗我玩儿呢?这时候大家别忘了,前面说过这方法已经在API 16过时了,要在高版本用的话需要关掉硬件加速,什么?硬件加速怎么关...

硬件加速分全局(Application)、Activity、Window、View 四个层级,也简单提及一下:

1.在AndroidManifest.xml文件为application标签添加如下的属性即可为整个应用程序开启硬件加速:

<application android:hardwareAccelerated="true" ...>

2.在Activity 标签下使用 hardwareAccelerated 属性开启或关闭硬件加速:

<activity android:hardwareAccelerated="false" />
3. 在Window 层级使用如下代码开启硬件加速:

getWindow().setFlags(    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
4.View 级别如下关闭硬件加速,view 层级上没法单独开启硬件加速:

setLayerType(View.LAYER_TYPE_SOFTWARE, null);
好了,原来如此,咱们直接在全局application 关闭硬件加速:

<application
        android:allowBackup="true"
        android:hardwareAccelerated="false"---

再看下效果:

技术分享



我们可以看到,现在蓝花已经变成红花了,已经实现了前面说的图标变色小需求,这时候有人可能会说了,这东西还有点用,如果我要在进行某些操作的时候让图标进行颜色的平滑过渡,该怎么办呢?比如viewPager从一页滑到另一页的时候,图标颜色渐变过渡,其实这样的需求只需要在上面的基础上稍加改动即可,提供下思路:

eg:view层级对外提供个接口,传入渐变比例,view里根据比例计算出当前色值,然后实时的把paint的色值更新,重新绘制即可!

好了,AvoidXfermode 就讲这些!

2. PixelXorXfermode

/**
 * PixelXorXfermode implements a simple pixel xor (op ^ src ^ dst).
 * This transformation does not follow premultiplied conventions, therefore
 * this mode *always* returns an opaque color (alpha == 255). Thus it is
 * not really usefull for operating on blended colors.
 */
@Deprecated
public class PixelXorXfermode extends Xfermode {

    public PixelXorXfermode(int opColor) {
        native_instance = nativeCreate(opColor);
    }

    private static native int nativeCreate(int opColor);
}

从上面的介绍上讲,这只是一个简单异或运算,这种变换不满足预乘公约,因此会总是返回一个不透明的颜色,所以对操作颜色混合不是特别的有效;

基于以上说明和这个类已经过时,讲解到此结束;

3. PorterDuffXfermode

这个类是setXfermode 方法的核心,也是图层混合模式里的核武器,通过它再加上我们的想象力,就能解决图形图像绘制里的很多问题,首先,还是看下方法说明:

    /**
     * Create an xfermode that uses the specified porter-duff mode.
     *
     * @param mode           The porter-duff mode that is applied
     */
    public PorterDuffXfermode(PorterDuff.Mode mode) {
        this.mode = mode;
        native_instance = nativeCreateXfermode(mode.nativeInt);
    }

使用 PorterDuff 模式创建一个图层混合模式,如此说来,我们的重心就转移到了 PorterDuff 身上;

PorterDuff 是啥,居然没法翻译,我靠,百思不得其姐,如果对图形图像学有所了解,肯定会知道,PorterDuff 实则是两个人名的的组合Thomas Porter 和 Tom Duff ,PorterDuff则是用于描述数字图像合成的基本手法,通过组合使用 Porter-Duff 操作,可完成任意 2D图像的合成;

听起来好像很屌炸天的样子,接下来我们一起来逐个看下这些模式:

  public class PorterDuff {

    // these value must match their native equivalents. See SkPorterDuff.h
    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      (12),
        /** [Sa + Da - Sa*Da,
             Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
        LIGHTEN     (13),
        /** [Sa * Da, Sc * Dc] */
        MULTIPLY    (14),
        /** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
        SCREEN      (15),
        /** Saturate(S + D) */
        ADD         (16),
        OVERLAY     (17);

        Mode(int nativeInt) {
            this.nativeInt = nativeInt;
        }

        /**
         * @hide
         */
        public final int nativeInt;
    }


可以看到里面通过枚举enum定义了18种混合模式,并且每种模式都写出了对应的计算方法:

其中 Sa 代表source alpha ,即源 alpha 值 ,Da 代表 Destination alpha ,即 目标alpha值 ,Sc 代表 source color ,即源色值 ,Dc 代表 Destination color ,即目标色值,并且这所有的计算都以像素为单位,在某一种混合模式下,对每一个像素的alpha 和 color 通过对应算法进行运算,得出新的像素值,进行展示;

接下来写个view 逐个对以上模式进行测试:

public class PorterDuffXfermodeView extends View {

    private Paint mPaint;
    private Bitmap mBottomBitmap, mTopBitmap;
    private Rect mBottomSrcRect, mBottomDestRect;
    private Rect mTopSrcRect, mTopDestRect;

    private Xfermode mPorterDuffXfermode;
    // 图层混合模式
    private PorterDuff.Mode mPorterDuffMode;
    // 总宽高
    private int mTotalWidth, mTotalHeight;
    private Resources mResources;

    public PorterDuffXfermodeView(Context context) {
        super(context);
        mResources = getResources();
        initBitmap();
        initPaint();
        initXfermode();
    }

    // 初始化bitmap
    private void initBitmap() {
        mBottomBitmap = ((BitmapDrawable) mResources.getDrawable(R.drawable.blue)).getBitmap();
        mTopBitmap = ((BitmapDrawable) mResources.getDrawable(R.drawable.red)).getBitmap();
    }

    // 初始化混合模式
    private void initXfermode() {
        mPorterDuffMode = PorterDuff.Mode.DST;
        mPorterDuffXfermode = new PorterDuffXfermode(mPorterDuffMode);
    }

    private void initPaint() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 背景铺白
        canvas.drawColor(Color.WHITE);
        // 保存为单独的层
        int saveCount = canvas.saveLayer(0, 0, mTotalWidth, mTotalHeight, mPaint,
                Canvas.ALL_SAVE_FLAG);
        // 绘制目标图
        canvas.drawBitmap(mBottomBitmap, mBottomSrcRect, mBottomDestRect, mPaint);
        // 设置混合模式
        mPaint.setXfermode(mPorterDuffXfermode);
        // 绘制源图
        canvas.drawBitmap(mTopBitmap, mTopSrcRect, mTopDestRect, mPaint);
        mPaint.setXfermode(null);
        canvas.restoreToCount(saveCount);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mTotalWidth = w;
        mTotalHeight = h;
        int halfHeight = h / 2;

        mBottomSrcRect = new Rect(0, 0, mBottomBitmap.getWidth(), mBottomBitmap.getHeight());
        // 矩形只画屏幕一半
        mBottomDestRect = new Rect(0, 0, mTotalWidth, halfHeight);

        mTopSrcRect = new Rect(0, 0, mTopBitmap.getWidth(), mTopBitmap.getHeight());
        mTopDestRect = new Rect(0, 0, mTotalWidth, mTotalHeight);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

}

原图如下:

技术分享技术分享


蓝色上下分别为完全不透明和半透区,红色左右为半透和完全不透明区,这样绘制时可以让所有的情况进行交叉,便于分析,先看下默认绘制效果:

技术分享

(1). CLEAR 

清除模式[0,0],即最终所有点的像素的alpha 和color 都为 0,所以画出来的效果只有白色背景:

技术分享


(2). SRC -[Sa , Sc]

只保留源图像的 alpha 和 color ,所以绘制出来只有源图,有时候会感觉分不清先绘制的是源图还是后绘制的是源图,这个时候可以这么记,先绘制的是目标图,不管任何时候,一定要做一个有目标的人,目标在前!

这个时候绘制的情形是只有屏幕上的红色圆;

技术分享

(3). DST -[ Da, Dc ]

同上类比,只保留目标图像的 alpha 和 color,所以绘制出来的只有目标图,也就是屏幕上的的蓝色图;

技术分享

(4).SRC_OVER - [ Sa +(1-Sa) * Da , Rc = Sc +( 1- Sa ) * Dc]

在目标图片顶部绘制源图像,从命名上也可以看出来就是把源图像绘制在上方;

技术分享

(5). DST_OVER - [Sa + ( 1 - Sa ) * Da ,Rc = Dc + ( 1 - Da ) * Sc ]

可以和 SRC_OVER 进行类比,将目标图像绘制在上方,这时候就会看到先绘制的蓝色盖在了红色圆的上面;

技术分享

(6). SRC_IN - [ Sa * Da , Sc * Da ]

在两者相交的地方绘制源图像,并且绘制的效果会受到目标图像对应地方透明度的影响;

技术分享

(7). DST_IN - [ Sa * Da , Sa * Dc ]

可以和SRC_IN 进行类比,在两者相交的地方绘制目标图像,并且绘制的效果会受到源图像对应地方透明度的影响;

技术分享

(8). SRC_OUT - [ Sa * ( 1 - Da ) , Sc * ( 1 - Da ) ]

从字面上可以理解为 在不相交的地方绘制 源图像,那么我们来看看效果是不是这样;

技术分享

这个效果似乎和上面说的不大一样,这个时候我们回归本源[ Sa * ( 1 - Da ) , Sc * ( 1 - Da ) ] ,从公式里可以看到 对应处的 color 是 Sc * ( 1 - Da ) ,如果相交处的目标色的alpha是完全不透明的,这时候源图像会完全被过滤掉,否则会受到相交处目标色 alpha 影响,呈现出对应色值,如果还有问题,大家可以对比上图和普通叠加图,再参考下上面公式理解;

所以该模式总结一下应该是:在不相交的地方绘制源图像,相交处根据目标alpha进行过滤,目标色完全不透明时则完全过滤,完全透明则不过滤;

(9). DST_OUT - [ Da * ( 1 - Sa ) , Dc * ( 1 - Sa ) ]

同样,可以类比SRC_OUT , 在不相交的地方绘制目标图像,相交处根据源图像alpha进行过滤,完全不透明处则完全过滤,完全透明则不过滤;

技术分享

(10). SRC_ATOP - [ Da , Sc * Da + ( 1 - Sa ) * Dc ]

源图像和目标图像相交处绘制源图像,不相交的地方绘制目标图像,并且相交处的效果会受到源图像和目标图像alpha的影响;

技术分享

(11). DST_ATOP  - [ Sa , Sa * Dc + Sc * ( 1 - Da ) ]

源图像和目标图像相交处绘制目标图像,不相交的地方绘制源图像,并且相交处的效果会受到源图像和目标图像alpha的影响;

技术分享

(12). XOR - [ Sa + Da - 2 * Sa * Da, Sc * ( 1 - Da ) + ( 1 - Sa ) * Dc ]

在不相交的地方按原样绘制源图像和目标图像,相交的地方受到对应alpha和色值影响,按上面公式进行计算,如果都完全不透明则相交处完全不绘制;

技术分享

(13). DARKEN - [ Sa + Da - Sa * Da , Sc * ( 1 - Da ) + Dc * ( 1 - Sa ) + min(Sc , Dc) ]

该模式处理过后,会感觉效果变暗,即进行对应像素的比较,取较暗值,如果色值相同则进行混合;

从算法上看,alpha值变大,色值上如果都不透明则取较暗值,非完全不透明情况下使用上面算法进行计算,受到源图和目标图对应色值和alpha值影响;

技术分享


同样的,这样的混合效果可以直接在PS里进行简单模拟,创建三个一样的图层,选择对应的混合模式,对于效果表示是一致的:


技术分享

(14). LIGHTEN - [ Sa + Da - Sa * Da , Sc * ( 1 -Da ) + Dc * ( 1 - Sa ) + max ( Sc , Dc ) ]

可以和 DARKEN 对比起来看,DARKEN 的目的是变暗,LIGHTEN 的目的则是变亮,如果在均完全不透明的情况下 ,色值取源色值和目标色值中的较大值,否则按上面算法进行计算;

技术分享


(15). MULTIPLY - [ Sa * Da , Sc * Dc ]

正片叠底,即查看每个通道中的颜色信息,并将基色与混合色复合。结果色总是较暗的颜色。任何颜色与黑色复合产生黑色。任何颜色与白色复合保持不变。当用黑色或白色以外的颜色绘画时,绘画工具绘制的连续描边产生逐渐变暗的颜色。

技术分享

(16). SCREEN - [ Sa + Da - Sa * Da , Sc + Dc - Sc * Dc ]

滤色,滤色模式与我们所用的显示屏原理相同,所以也有版本把它翻译成“屏幕”;

简单的说就是保留两个图层中较白的部分,较暗的部分被遮盖;

当一层使用了滤色(屏幕)模式时,图层中纯黑的部分变成完全透明,纯白部分完全不透明,其他的颜色根据颜色级别产生半透明的效果;

技术分享
(17). ADD - [ Saturate( S+ D ) ]

 饱和度叠加
(18). OVERLAY - 叠加
像素是进行 Multiply (正片叠底)混合还是 Screen (屏幕)混合,取决于底层颜色,但底层颜色的高光与阴影部分的亮度细节会被保留;
以上就是android 提供的所有的混合模式,有了这,只要是图像与图像任何形式混合能创造的效果,我们就都能够进行实现,这时候我们再看看混合模式的示意图

技术分享

前面我们用AvoidXfermode实现的图标变色小栗子也同样可以使用PorterDuffXfermode进行实现,大家可以自己尝试;

最后,我们利用混合模式完成两个小栗子:

一、还算比较流行的一种 loading 形式:

先看下效果:

技术分享

这个效果如果不知道图像的混合模式,做的话可能会采用2张图,对上面的图进行裁切,但自从有了混合模式,这一切都将变的简单;

我们只需要如下几步:

1.绘制目标图;

2.设置混合模式;

3.绘制带颜色的Rect;

4.不断改变Rect的区域;

代码如下:

public class PoterDuffLoadingView extends View {

    private Resources mResources;
    private Paint mBitPaint;
    private Bitmap mBitmap;

    private int mTotalWidth, mTotalHeight;
    private int mBitWidth, mBitHeight;
    private Rect mSrcRect, mDestRect;
    private PorterDuffXfermode mXfermode;

    private Rect mDynamicRect;
    private int mCurrentTop;
    private int mStart, mEnd;

    public PoterDuffLoadingView(Context context) {
        super(context);
        mResources = getResources();
        initBitmap();
        initPaint();
        initXfermode();
    }

    private void initXfermode() {
        // 叠加处绘制源图
        mXfermode = new PorterDuffXfermode(Mode.SRC_IN);
    }

    private void initPaint() {
        // 初始化paint
        mBitPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBitPaint.setFilterBitmap(true);
        mBitPaint.setDither(true);
        mBitPaint.setColor(Color.RED);
    }

    private void initBitmap() {
        // 初始化bitmap
        mBitmap = ((BitmapDrawable) mResources.getDrawable(R.drawable.ga_studio))
                .getBitmap();
        mBitWidth = mBitmap.getWidth();
        mBitHeight = mBitmap.getHeight();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 存为新图层
        int saveLayerCount = canvas.saveLayer(0, 0, mTotalWidth, mTotalHeight, mBitPaint,
                Canvas.ALL_SAVE_FLAG);
        // 绘制目标图
        canvas.drawBitmap(mBitmap, mSrcRect, mDestRect, mBitPaint);
        // 设置混合模式
        mBitPaint.setXfermode(mXfermode);
        // 绘制源图形
        canvas.drawRect(mDynamicRect, mBitPaint);
        // 清除混合模式
        mBitPaint.setXfermode(null);
        // 恢复保存的图层;
        canvas.restoreToCount(saveLayerCount);

        // 改变Rect区域,真实情况下时提供接口传入进度,计算高度
        mCurrentTop -= 8;
        if (mCurrentTop <= mEnd) {
            mCurrentTop = mStart;
        }
        mDynamicRect.top = mCurrentTop;
        postInvalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mTotalWidth = w;
        mTotalHeight = h;
        mSrcRect = new Rect(0, 0, mBitWidth, mBitHeight);
        // 让左边和上边有些距离
        int left = (int) TypedValue.complexToDimension(20, mResources.getDisplayMetrics());
        mDestRect = new Rect(left, left, left + mBitWidth, left + mBitHeight);
        mStart = left + mBitHeight;
        mCurrentTop = mStart;
        mEnd = left;
        mDynamicRect = new Rect(left, mStart, left + mBitWidth, left + mBitHeight);
    }
}

二、采用混合模式过滤形状,实现水波纹:

技术分享


文章链接 - 自定义view实现水波纹效果











郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。