在使用Paint的时候,我们能通过使用Xfermode能够完成图像组合的效果将绘制的图形的像素和Canvas上对应位置的像素按照一定的规则进行混合,形成新的像素,再更新到Canvas中形成最终的图形。

在设置Xfermode模式之前绘制的称为目标图像(DST), 在设置Xfermode模式之后绘制的称为源图像(SRC)!
所以先绘制的称为DST,后绘制的称为SRC

Alpha compositing modes

模式 图像 备注
Source composite_SRC 只显示源图像
Source Over composite_SRC_OVER 将源图像放在目标图像上方
Source In composite_SRC_IN 只在源图像和目标图像相交的地方绘制【源图像】
Source Atop composite_SRC_ATOP 在源图像和目标图像相交的地方绘制【源图像】,
在不相交的地方绘制【目标图像】,
相交处的效果受到源图像和目标图像alpha的影响
Destination composite_DST 只显示目标图像
Destination Over composite_DST_OVER 将目标图像放在源图像上方
Destination In composite_DST_IN 只在源图像和目标图像相交的地方绘制【目标图像】,
绘制效果受到源图像对应地方透明度影响
Destination Atop composite_DST_ATOP 在源图像和目标图像相交的地方绘制【目标图像】
,在不相交的地方绘制【源图像】,
相交处的效果受到源图像和目标图像alpha的影响
Clear composite_CLEAR 清除图像
Source Out composite_SRC_OUT 只在源图像和目标图像不相交的地方绘制【源图像】,
相交的地方根据目标图像的对应地方的alpha进行过滤,
目标图像完全不透明则完全过滤,完全透明则不过滤
Destination Out composite_DST_OUT 只在源图像和目标图像不相交的地方绘制【目标图像】,
在相交的地方根据源图像的alpha进行过滤,
源图像完全不透明则完全过滤,完全透明则不过滤
Exclusive Or composite_XOR 在源图像和目标图像相交的地方之外绘制它们,
在相交的地方受到对应alpha和色值影响,如果完全不透明则相交处完全不绘制

Blending modes

模式 图像 备注
Darken composite_DARKEN 变暗,较深的颜色覆盖较浅的颜色,若两者深浅程度相同则混合
Lighten composite_LIGHTEN 变亮,与DARKEN相反,DARKEN和LIGHTEN生成的图像结果与Android对颜色值深浅的定义有关
Multiply composite_MULTIPLY 正片叠底,源图像素颜色值乘以目标图像素颜色值除以255得到混合后图像像素颜色值
Screen composite_SCREEN 滤色,色调均和,保留两个图层中较白的部分,较暗的部分被遮盖
Overlay composite_OVERLAY 叠加

通过上表,我们能够很明显的知道我们的XFermode其实实际上就是在使用图形之间的相互组合以达到自己的想要的目的, 他提供了17种组合模式。

阅读源码的过程中,发现Xfermode的子类只有一个子类PorterDuffXfermode

public class Xfermode {
    static final int DEFAULT = PorterDuff.Mode.SRC_OVER.nativeInt;
    int porterDuffMode = DEFAULT;
}
public class PorterDuffXfermode extends Xfermode {
    /**
     * Create an xfermode that uses the specified porter-duff mode.
     *
     * @param mode           The porter-duff mode that is applied
     */
    public PorterDuffXfermode(PorterDuff.Mode mode) {
        porterDuffMode = mode.nativeInt;
    }
}

使用

val paint = Paint()
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)

倒影效果

image-20201227171017960
class ReflectionView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    // 头像bitmap
    private val avatarBmp = BitmapFactory.decodeResource(resources, R.drawable.avator)

    // 原图像
    private val srcBitmap = BitmapFactory.decodeResource(resources, R.drawable.invert_shade)

    // 目标图像
    private val invertBitmap = run {
        // 图像倒置
        val matrix = Matrix()
        matrix.setScale(1f, -1f)
        Bitmap.createBitmap(
            avatarBmp,
            0,
            0,
            avatarBmp.width,
            avatarBmp.height,
            matrix,
            true
        )
    }

    private val paint = Paint()

    // xfermode 模式  dst-in 只显示两个图像交汇部分,并绘制目标图像, 绘制图像的效果
    // 受到原图像的透明度的影响
    private val xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawBitmap(avatarBmp, 0f, 0f, paint)
        val layerId =
            canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG)
        canvas.translate(0f, avatarBmp.height.toFloat())
        // 先绘制的是目标图像
        canvas.drawBitmap(invertBitmap, 0f, 0f, paint)
        paint.xfermode = xfermode
        // 原图像
        canvas.drawBitmap(srcBitmap, 0f, 0f, paint)

        paint.xfermode = null
        canvas.restoreToCount(layerId)
    }
}

心电图

image-20201227180106704

ps: 此图只是动态图的一帧。

class HeartView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    // 目标图像 --> 心电图
    private val dstBmp = BitmapFactory.decodeResource(resources, R.drawable.heartmap)

    // 源图像
    private val srcBmp = Bitmap.createBitmap(dstBmp.width, dstBmp.height, Bitmap.Config.ARGB_8888)

    // 画笔
    private val paint = Paint()


    // xfermode
    private val xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)

    // 创建一个canvas,用于绘制源bitmap
    private val srcCanvas = Canvas(srcBmp)

    private var dx = 0f

    private val animator = ValueAnimator.ofFloat(0.2f * srcBmp.width, srcBmp.width.toFloat())
        .apply {
            repeatCount = ValueAnimator.INFINITE
            interpolator = LinearInterpolator()
            duration = 6000L
            addUpdateListener {
                dx = it.animatedValue as Float
                postInvalidate()
            }
            start()
        }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        srcCanvas.drawColor(Color.RED, PorterDuff.Mode.CLEAR)
        srcCanvas.drawRect(0f, 0f, srcBmp.width.toFloat(), srcBmp.height.toFloat(), paint)

        val layerId =
            canvas.saveLayer(
                width.toFloat() - dx,
                0f,
                width.toFloat(),
                height.toFloat(),
                null,
                Canvas.ALL_SAVE_FLAG
            )

        // 先绘制目标图像
        canvas.drawBitmap(dstBmp, 0f, 0f, paint)
        paint.xfermode = xfermode
        // 绘制源图像
        canvas.drawBitmap(srcBmp, 0f, 0f, paint)
        paint.xfermode = null
        canvas.restoreToCount(layerId)
    }
}

波浪图

image-20201227184438008
ps: 此图只是动态图的一帧。

class WaveView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val waveBmp = BitmapFactory.decodeResource(resources, R.drawable.wav)
    private val shadeBmp = BitmapFactory.decodeResource(resources, R.drawable.circle_shape)

    private val paint = Paint().apply {
        isAntiAlias = true
    }

    private val xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)

    private val srcRect = Rect()

    private val dstRect = Rect()

    private var dx: Int = (0.15f * shadeBmp.height).toInt()

    private val animator = ValueAnimator.ofInt(0, shadeBmp.width)
        .apply {
            repeatCount = ValueAnimator.INFINITE
            interpolator = LinearInterpolator()
            duration = 2000L
            addUpdateListener {
                dx = it.animatedValue as Int
                postInvalidate()
            }
            start()
        }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawBitmap(shadeBmp, 0f, 0f, paint)

        val saveLayer =
            canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG)
        // 截图波浪图内容到圆中
        srcRect.set(dx, 0, shadeBmp.width + dx, shadeBmp.height)
        dstRect.set(0, 0, shadeBmp.width, shadeBmp.height)
        canvas.drawBitmap(waveBmp, srcRect, dstRect, paint)
        paint.xfermode = xfermode
        canvas.drawBitmap(shadeBmp, 0f, 0f, paint)
        paint.xfermode = null
        canvas.restoreToCount(saveLayer)
    }
}

刮刮卡

image-20201227211238293
class GuaGuaCardView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val textBmp = BitmapFactory.decodeResource(resources, R.drawable.guaguaka_text1)
    private val guaBmp = BitmapFactory.decodeResource(resources, R.drawable.guaguaka)
    private val dstBmp = Bitmap.createBitmap(guaBmp.width, guaBmp.height, Bitmap.Config.ARGB_8888)

    private val paint = Paint()
    private val path = Path()
    private val xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)
    private val pathCanvas = Canvas(dstBmp)

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawBitmap(textBmp, 0f, 0f, paint)

        val layerId = canvas.saveLayer(
            0f, 0f,
            width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG
        )
        pathCanvas.drawPath(path, paint)
        canvas.drawBitmap(dstBmp, 0f, 0f, paint)
        paint.xfermode = xfermode
        canvas.drawBitmap(guaBmp, 0f, 0f, paint)
        paint.xfermode = null
        canvas.restoreToCount(layerId)
    }

    private var preX = 0f
    private var preY = 0f

    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                preX = event.x
                preY = event.y
                path.moveTo(preX, preY)
                return true
            }
            MotionEvent.ACTION_MOVE -> {
                if (event.x > guaBmp.width || event.y > guaBmp.height) {
                    return false
                }
                val endX = (preX + event.x) / 2f
                val endY = (preY + event.y) / 2f
                path.quadTo(preX, preY, endX, endY)
                preX = event.x
                preY = event.y
            }
        }
        postInvalidate()
        return super.onTouchEvent(event)
    }
}

橡皮擦

image-20201227215738569
class EraserView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val srcBmp = BitmapFactory.decodeResource(resources, R.drawable.avator)
    private val dstBmp = Bitmap.createBitmap(srcBmp.width, srcBmp.height, Bitmap.Config.ARGB_8888)
    private val paint = Paint()
    private val xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)
    private val pathCanvas = Canvas(dstBmp)
    private val path = Path()


    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val layerId = canvas.saveLayer(
            0f, 0f, width.toFloat(), height.toFloat(),
            null, Canvas.ALL_SAVE_FLAG
        )
        pathCanvas.drawPath(path, paint)
        canvas.drawBitmap(dstBmp, 0f, 0f, paint)
        paint.xfermode = xfermode
        canvas.drawBitmap(srcBmp, 0f, 0f, paint)
        paint.xfermode = null
        canvas.restoreToCount(layerId)
    }

    private var preX = 0f
    private var preY = 0f

    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                preX = event.x
                preY = event.y
                path.moveTo(preX, preY)
                return true
            }

            MotionEvent.ACTION_MOVE -> {
                val endX = (preX + event.x) / 2f
                val endY = (preY + event.y) / 2f
                path.quadTo(preX, preY, endX, endY)
                preX = event.x
                preY = event.y
            }
        }
        postInvalidate()
        return super.onTouchEvent(event)

    }
}

本文部分内容参考地址:https://blog.csdn.net/crazy1235/article/details/73835933#t1