Paint API —— setXfermode() 详解

Posted 我想月薪过万

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Paint API —— setXfermode() 详解相关的知识,希望对你有一定的参考价值。

效果展示

 API解释

setXfermode(Xfermode xfermode)

@param xfermode May be null. The xfermode to be installed in the paint

译文:xfermode 可以为空,并且必须安装到画笔上

Xfermode对象由 PorterDuffXfermode(PorterDuff.Mode mode)构造方法产生

PorterDuff.Mode mode 有18种


使用步骤:

第一步:绘制 目标(dst)图
第二步: 给 画笔 设置模式
第三步:绘制 源 (src) 图
第四步:清除 画笔 模式

注意: 绘制 dst 和 src 的时候要指定画笔 并且是设置了模式的同一个画笔

有兄弟说上面只有16种,对,说明你数学很好。

还有两种是 新增的,分别为 ADDOVERLAY两种模式

模式讲解

1)PorterDuff.Mode.ADD:

 饱和度叠加

2)PorterDuff.Mode.CLEAR:

 清除

3)PorterDuff.Mode.DARKEN:

 取两图层全部区域,交集部分颜色加深

4)PorterDuff.Mode.DST:

 只保留目标图的alpha和color,所以绘制出来只有目标图

5)PorterDuff.Mode.DST_ATOP:

 源图和目标图相交处绘制目标图,不相交的地方绘制源图

6)PorterDuff.Mode.DST_IN:

 两者相交的地方绘制目标图,绘制的效果会受到原图处的透明度影响

7)PorterDuff.Mode.DST_OUT:

 在不相交的地方绘制目标图

8)PorterDuff.Mode.DST_OVER:

 目标图绘制在上方

9)PorterDuff.Mode.LIGHTEN:

 取两图层全部区域,点亮交集部分颜色

10)PorterDuff.Mode.MULTIPLY:

 取两图层交集部分叠加后颜色

11)PorterDuff.Mode.OVERLAY:

 叠加

12)PorterDuff.Mode.SCREEN:

 取两图层全部区域,交集部分变为透明色

13)PorterDuff.Mode.SRC:

 只保留源图像的alpha和color,所以绘制出来只有源图

14)PorterDuff.Mode.SRC_ATOP:

 源图和目标图相交处绘制源图,不相交的地方绘制目标图

15)PorterDuff.Mode.SRC_IN:

 两者相交的地方绘制源图

16)PorterDuff.Mode.SRC_OUT:

 不相交的地方绘制源图

17)PorterDuff.Mode.SRC_OVER:

 把源图绘制在上方

18)PorterDuff.Mode.XOR:

 不相交的地方按原样绘制源图和目标图

 代码展示

自定义布局java代码

package com.wust.xfmode;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;

import androidx.annotation.Nullable;

/**
 * ClassName: MyXfModeView <br/>
 * Description: <br/>
 * date: 2021/8/9 17:29<br/>
 *
 * @author yiqi<br />
 * @QQ 1820762465
 * @微信 yiqiideallife
 * @技术交流QQ群 928023749
 */
public class MyXfModeView extends View {

    private Paint paint;
    private Bitmap bitmap;
    //定义模式
    private PorterDuffXfermode pdXfermode;
    private Bitmap dstBm;
    private Bitmap srcBm;
    private int screenWidth;
    private int screenHeight;
    //定义模式 Array
    private PorterDuff.Mode[] modes = {
      PorterDuff.Mode.ADD,PorterDuff.Mode.CLEAR,PorterDuff.Mode.DARKEN,PorterDuff.Mode.DST,PorterDuff.Mode.DST_ATOP,
      PorterDuff.Mode.DST_IN,PorterDuff.Mode.DST_OUT,PorterDuff.Mode.DST_OVER,PorterDuff.Mode.LIGHTEN,PorterDuff.Mode.MULTIPLY,
      PorterDuff.Mode.OVERLAY,PorterDuff.Mode.SCREEN,PorterDuff.Mode.SRC,PorterDuff.Mode.SRC_ATOP,PorterDuff.Mode.SRC_IN,
      PorterDuff.Mode.SRC_OUT,PorterDuff.Mode.SRC_OVER,PorterDuff.Mode.XOR
    };
    private int mCurMode = 0;

    public MyXfModeView(Context context) {
        super(context);
    }

    public MyXfModeView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initPaint();
        initData();
    }

    public MyXfModeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint();
        initData();
    }

    private void initData() {
        //获取屏幕宽高
        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
        screenWidth = displayMetrics.widthPixels;
        screenHeight = displayMetrics.heightPixels;
        //获取两张 bm 图
        dstBm = makeDst(screenWidth, screenHeight);
        srcBm = makeSrc(screenWidth, screenHeight);
    }

    private void initPaint() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //绘制圆图形
        canvas.drawBitmap(dstBm,0,0,null);
        canvas.drawBitmap(srcBm,0,0,null);
        //绘制分界线
        canvas.drawLine(0,screenHeight*2/4,screenWidth,screenHeight*2/4,paint);
        //创建图层 sc保存着创建图层前的画布状态
        RectF bounds = new RectF(0,screenHeight/2,screenWidth,screenHeight);
        int sc = canvas.saveLayer(bounds, null);
        //必须先 绘制 目标 bm
        canvas.drawBitmap(dstBm,0,screenHeight/2,paint);
        //然后 设置模式
        pdXfermode = new PorterDuffXfermode(modes[mCurMode]);
        paint.setXfermode(pdXfermode);
        //绘制 目标 bm
        canvas.drawBitmap(srcBm,0,screenHeight/2,paint);
        //一定要记得 模式用完之后去除
        paint.setXfermode(null);
        canvas.restoreToCount(sc);

    }

    //创建目标 bm 圆形
    private Bitmap makeDst(int w,int h){
        Bitmap bitmap = Bitmap.createBitmap(w,h, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setColor(0xFF26AAD1);
        canvas.drawOval(w * 1 / 4, w * 1 / 4,w * 1 / 2, w * 1 / 2,p);
        return bitmap;
    }

    //创建源 bm 矩形
    private Bitmap makeSrc(int w,int h){
        Bitmap bitmap = Bitmap.createBitmap(w,h, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setColor(0xFFFFCE43);
        canvas.drawRect(w *3/ 8, w *3/ 8, w * 5/8, w *5/8, p);
        return bitmap;
    }

    //暴露设置模式的方法
    public void setMode(int mode){
        this.mCurMode = mode;
        invalidate();
    }
}

xml布局代码

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_dscript"
        android:layout_centerInParent="true"
        android:layout_width="match_parent"
        android:layout_height="180dp"
        android:paddingTop="90dp"
        android:textColor="#F15A5A"
        android:gravity="center"/>

    <com.wust.xfmode.MyXfModeView
        android:id="@+id/mxmv_xfmode"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

MainActivity.java

package com.wust.xfmode;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private MyXfModeView mxmv_xfmode;
    private TextView tv_dscript;

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

    private void bindData() {

    }

    private void bindView() {
        mxmv_xfmode = findViewById(R.id.mxmv_xfmode);
        tv_dscript = findViewById(R.id.tv_dscript);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.mode_menu,menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()){
            case R.id.menu_add:
            {
                mxmv_xfmode.setMode(0);
                tv_dscript.setText("PorterDuff.Mode.ADD:饱和度叠加");
            }
            break;
            case R.id.menu_clear:
            {
                mxmv_xfmode.setMode(1);
                tv_dscript.setText("PorterDuff.Mode.CLEAR:清除");
            }
            break;
            case R.id.menu_darken:
            {
                mxmv_xfmode.setMode(2);
                tv_dscript.setText("PorterDuff.Mode.DARKEN:取两图层全部区域,交集部分颜色加深");
            }
            break;
            case R.id.menu_dst:
            {
                mxmv_xfmode.setMode(3);
                tv_dscript.setText("PorterDuff.Mode.DST:只保留目标图的alpha和color,所以绘制出来只有目标图");
            }
            break;
            case R.id.menu_dst_atop:
            {
                mxmv_xfmode.setMode(4);
                tv_dscript.setText("PorterDuff.Mode.DST_ATOP:源图和目标图相交处绘制目标图,不相交的地方绘制源图");
            }
            break;
            case R.id.menu_dst_in:
            {
                mxmv_xfmode.setMode(5);
                tv_dscript.setText("PorterDuff.Mode.DST_IN:两者相交的地方绘制目标图,绘制的效果会受到原图处的透明度影响");
            }
            break;
            case R.id.menu_dst_out:
            {
                mxmv_xfmode.setMode(6);
                tv_dscript.setText("PorterDuff.Mode.DST_OUT:在不相交的地方绘制目标图");
            }
            break;
            case R.id.menu_dst_over:
            {
                mxmv_xfmode.setMode(7);
                tv_dscript.setText("PorterDuff.Mode.DST_OVER:目标图绘制在上方");
            }
            break;
            case R.id.menu_lighten:
            {
                mxmv_xfmode.setMode(8);
                tv_dscript.setText("PorterDuff.Mode.LIGHTEN:取两图层全部区域,点亮交集部分颜色");
            }
            break;
            case R.id.menu_multiply:
            {
                mxmv_xfmode.setMode(9);
                tv_dscript.setText("PorterDuff.Mode.MULTIPLY:取两图层交集部分叠加后颜色");
            }
            break;
            case R.id.menu_overlay:
            {
                mxmv_xfmode.setMode(10);
                tv_dscript.setText("PorterDuff.Mode.OVERLAY:叠加");
            }
            break;
            case R.id.menu_screen:
            {
                mxmv_xfmode.setMode(11);
                tv_dscript.setText("PorterDuff.Mode.SCREEN:取两图层全部区域,交集部分变为透明色");
            }
            break;
            case R.id.menu_src:
            {
                mxmv_xfmode.setMode(12);
                tv_dscript.setText("PorterDuff.Mode.SRC:只保留源图像的alpha和color,所以绘制出来只有源图");
            }
            break;
            case R.id.menu_src_atop:
            {
                mxmv_xfmode.setMode(13);
                tv_dscript.setText("PorterDuff.Mode.SRC_ATOP:源图和目标图相交处绘制源图,不相交的地方绘制目标图");
            }
            break;
            case R.id.menu_src_in:
            {
                mxmv_xfmode.setMode(14);
                tv_dscript.setText("PorterDuff.Mode.SRC_IN:两者相交的地方绘制源图");
            }
            break;
            case R.id.menu_src_out:
            {
                mxmv_xfmode.setMode(15);
                tv_dscript.setText("PorterDuff.Mode.SRC_OUT:不相交的地方绘制源图");
            }
            break;
            case R.id.menu_src_over:
            {
                mxmv_xfmode.setMode(16);
                tv_dscript.setText("PorterDuff.Mode.SRC_OVER:把源图绘制在上方");
            }
            break;
            case R.id.menu_xor:
            {
                mxmv_xfmode.setMode(17);
                tv_dscript.setText("PorterDuff.Mode.XOR:不相交的地方按原样绘制源图和目标图");
            }
            break;
        }
        return super.onOptionsItemSelected(item);
    }
}

menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/menu_add" android:title="PorterDuff.Mode.ADD"/>
    <item android:id="@+id/menu_clear" android:title="PorterDuff.Mode.CLEAR"/>
    <item android:id="@+id/menu_darken" android:title="PorterDuff.Mode.DARKEN"/>
    <item android:id="@+id/menu_dst" android:title="PorterDuff.Mode.DST"/>
    <item android:id="@+id/menu_dst_atop" android:title="PorterDuff.Mode.DST_ATOP"/>
    <item android:id="@+id/menu_dst_in" android:title="PorterDuff.Mode.DST_IN"/>
    <item android:id="@+id/menu_dst_out" android:title="PorterDuff.Mode.DST_OUT"/>
    <item android:id="@+id/menu_dst_over" android:title="PorterDuff.Mode.DST_OVER"/>
    <item android:id="@+id/menu_lighten" android:title="PorterDuff.Mode.LIGHTEN"/>
    <item android:id="@+id/menu_multiply" android:title="PorterDuff.Mode.MULTIPLY"/>
    <item android:id="@+id/menu_overlay" android:title="PorterDuff.Mode.OVERLAY"/>
    <item android:id="@+id/menu_screen" android:title="PorterDuff.Mode.SCREEN"/>
    <item android:id="@+id/menu_src" android:title="PorterDuff.Mode.SRC"/>
    <item android:id="@+id/menu_src_atop" android:title="PorterDuff.Mode.SRC_ATOP"/>
    <item android:id="@+id/menu_src_in" android:title="PorterDuff.Mode.SRC_IN"/>
    <item android:id="@+id/menu_src_out" android:title="PorterDuff.Mode.SRC_OUT"/>
    <item android:id="@+id/menu_src_over" android:title="PorterDuff.Mode.SRC_OVER"/>
    <item android:id="@+id/menu_xor" android:title="PorterDuff.Mode.XOR"/>
</menu>

出错点

你在绘制的过程中可能会发现与预期结果不一样,如下:

错误图

正确图

其中的解决方法有两种:

1、给画布添加背景

2、添加图层

以上是关于Paint API —— setXfermode() 详解的主要内容,如果未能解决你的问题,请参考以下文章

Android Shader 颜色图像渲染 paint.setXfermode

Android Paint之 setXfermode PorterDuffXfermode 讲解

Android Paint Xfermode 学习小结

Canvas清空

Android 自定义 View 进阶 - Xfermode

Android自己定义View画家(画布)Canvas与画笔Paint的应用——绘图涂鸦板app的实现