Paint 的 Xfermode 的用法

Xfermode 指的是你要绘制的内容和 Canvas 的目标位置的内容应该怎样结合计算出最终的颜色。但通俗地说,其实就是要你以绘制的内容作为源图像,以 View 中已有的内容作为目标图像,选取一个 PorterDuff.Mode 作为绘制内容的颜色处理方案。

  • 怎么用?
    首先我们来看看Paint类中的setXfermode()方法:
    public Xfermode setXfermode(Xfermode xfermode) {
        long xfermodeNative = 0;
        if (xfermode != null)
            xfermodeNative = xfermode.native_instance;
        nSetXfermode(mNativePaint, xfermodeNative);
        mXfermode = xfermode;
        return xfermode;
    }

可见,它的参数是一个Xfermode对象,Xfermode还有一个子类:PorterDuffXfermode,我们一般在用的时候,都是用PorterDuffXfermode

这里写图片描述

现在我们来创建一个PorterDuffXfermode对象,首先来看看它的构造方法,

PorterDuffXfermode(PorterDuff.Mode mode)

可见,需要传一个PorterDuff.Mode,那这个PorterDuff.Mode又是什么东西呢?我们点进去看:

public class PorterDuff {

    // these value must match their native equivalents. See SkXfermode.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      (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;
        }

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

    /**
     * @hide
     */
    public static final int modeToInt(Mode mode) {
        return mode.nativeInt;
    }

    /**
     * @hide
     */
    public static final Mode intToMode(int val) {
        switch (val) {
            default:
            case  0: return Mode.CLEAR;
            case  1: return Mode.SRC;
            case  2: return Mode.DST;
            case  3: return Mode.SRC_OVER;
            case  4: return Mode.DST_OVER;
            case  5: return Mode.SRC_IN;
            case  6: return Mode.DST_IN;
            case  7: return Mode.SRC_OUT;
            case  8: return Mode.DST_OUT;
            case  9: return Mode.SRC_ATOP;
            case 10: return Mode.DST_ATOP;
            case 11: return Mode.XOR;
            case 16: return Mode.DARKEN;
            case 17: return Mode.LIGHTEN;
            case 13: return Mode.MULTIPLY;
            case 14: return Mode.SCREEN;
            case 12: return Mode.ADD;
            case 15: return Mode.OVERLAY;
        }
    }
}

它其实就是一个枚举类,有18种值,我们在传参的时候,选择其中一个就可以达到相应的效果,Google给我们了一张图,显示的是两个图形一圆一方通过一定的计算产生不同的组合效果,其中圆形是底部的目标图像,方形是上方的源图像。

这里写图片描述

实例代码:

        //禁用硬件加速
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);

        Bitmap dstBitmap = getDstBitmap(200, 200);
        Bitmap srcBitmap = getSrcBitmap(200, 200);

        //设置离屏缓冲
        int saveLayer = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);

        canvas.drawBitmap(dstBitmap, 100, 100, mPaint);
        if (porterDuffMode != null) {
            mPaint.setXfermode(new PorterDuffXfermode(//混合模式));
        }
        canvas.drawBitmap(srcBitmap, 100, 100, mPaint);
        mPaint.setXfermode(null);
        canvas.restoreToCount(saveLayer);
  • 要注意什么?

    1、禁用硬件加速:

setLayerType(View.LAYER_TYPE_SOFTWARE, null); 

2、使用离屏缓冲

 //设置离屏缓冲
int saveLayer = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);

//todo 核心绘制代码

//还原图层
canvas.restoreToCount(saveLayer);

3、两个bitmap的大小要一直,否则混合之后达到的效果可能不是你想要的

这里写图片描述

demo的全部代码如下:

1.自定义View

public class CustomView extends View {

    public static final String TAG="CustomView";
    private PorterDuff.Mode porterDuffMode;
    private Paint mPaint= new Paint();

    public CustomView(Context context) {
        this(context, null);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

    }

    public void setPorterDuffMode(PorterDuff.Mode porterDuffMode) {
        this.porterDuffMode = porterDuffMode;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //禁用硬件加速
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);

        Bitmap dstBitmap = getDstBitmap(200, 200);
        Bitmap srcBitmap = getSrcBitmap(200, 200);

        //设置离屏缓冲
        int saveLayer = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);

        canvas.drawBitmap(dstBitmap,100,100,mPaint);
        if (porterDuffMode!=null) {
            mPaint.setXfermode(new PorterDuffXfermode(porterDuffMode));
        }
        canvas.drawBitmap(srcBitmap,100,100,mPaint);
        mPaint.setXfermode(null);
        canvas.restoreToCount(saveLayer);
    }


    private Bitmap getDstBitmap(int w,int h){
        Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.RED);
        canvas.drawCircle(w/2,h/2,w/3,paint);
        return bitmap;
    }

    private Bitmap getSrcBitmap(int w,int h){
        Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLUE);
        canvas.drawRect(new Rect(0,0,w*2/3,h*2/3),paint);
        return bitmap;
    }
}

2.在布局文件中使用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.zomll.customviewdemo.MainActivity">

    <com.zomll.customviewdemo.CustomView
        android:id="@+id/cv"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>
    <android.support.v7.widget.AppCompatSpinner
        android:id="@+id/spiner"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

3.引用该布局文件

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        AppCompatSpinner spiner = (AppCompatSpinner) findViewById(R.id.spiner);
        final CustomView cv = (CustomView) findViewById(R.id.cv);

        ArrayList<String> datas=new ArrayList<>();
        for (int i = 0; i < PorterDuff.Mode.values().length; i++) {
            datas.add(PorterDuff.Mode.values()[i].name());
        }

        spiner.setAdapter(new ArrayAdapter<>(this,android.R.layout.simple_spinner_dropdown_item,datas));

        spiner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                cv.setPorterDuffMode(PorterDuff.Mode.values()[position]);
                cv.invalidate();
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });
    }
}

运行效果如下,可以切换不同的模式来查看相应的效果:

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值