Xfermode究竟是怎么回事,为什么我得到的结果跟网上的图不一样呢?

Xfermode全名Transfer mode,其作用是实现两张图叠加时的混合效果。

这里普及一个知识,美国人写 trans 这个音的时候喜欢用 X 这个字母代替(其实就是两者读音相近),就好像我们写 dpToPx 可以变成 dp2Px,因为2的英文读音和To是很接近的。所以Transfer mode 就是Xfer mode,那去掉空格就是 Xfermode,这里没有什么意思,就是一种简写。

网上流传的关于Xfermode最出名的图来源于AndroidSDK的samples中,名叫Xfermodes.java,效果如下:

官方文档在这里:PorterDuff.Mode,有兴趣的可以去这里看看

有很多人在实际使用时发现跟这张图表现的不一样,就觉得很奇怪。那究竟原因在哪呢?我们来看下官方文档里是怎么回事呢

上图截屏自官方文档,乍一看好像感觉不出什么来特别的。其实那么我们仔细阅读一下文档,会发现文档内其实给了两张图,这两张图除了蓝色和红色区域的其他部分是透明的部分(通常这种灰白格子就表示透明),很多人可能理解的就是只画有色部分的意思,但就是这一点导致了实际使用效果与文档描述不相符。

接下来我写两个View,一个用来只画有色区域,一个也要画出这张图中的透明部分

第一个只画有色区域的:

class XfermodeView1(context: Context, attrs: AttributeSet?) : View(context, attrs) {
    val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    var xfermode :PorterDuffXfermode ?= null
        set(value) {
            invalidate()
            field = value
        }
    override fun onDraw(canvas: Canvas) {
        val count = canvas.saveLayer(0f,0f,150.dp,150.dp,null)
        paint.color = OVAL_COLOR
        canvas.drawOval(0.dp,0f,100.dp,100.dp,paint)
        paint.xfermode = xfermode
        paint.color = RECT_COLOR
        canvas.drawRect(50.dp,50.dp,150.dp,150.dp,paint)
        paint.xfermode = null
        canvas.restoreToCount(count)
    }
}

第二个画出透明部分:

class XfermodeView2(context: Context, attrs: AttributeSet?) : View(context, attrs) {
    val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    var xfermode :PorterDuffXfermode ?= null
        set(value) {
            invalidate()
            field = value
        }


    val sourceImage =
        Bitmap.createBitmap(150.dp.toInt(), 150.dp.toInt(), Bitmap.Config.ARGB_8888)
    val destinationImage =
        Bitmap.createBitmap(150.dp.toInt(), 150.dp.toInt(), Bitmap.Config.ARGB_8888)

    init {
        val canvas = Canvas(destinationImage)
        paint.color = OVAL_COLOR
        //下边起点x是0,是因为我要按照文档的图把圆画到右上角
        canvas.drawOval(0f, 0f, 100.dp, 100.dp, paint)
        canvas.setBitmap(sourceImage)
        paint.color = RECT_COLOR
        //下边起点y是50,是因为我要按照文档的图把正方形画到左下角
        canvas.drawRect(50.dp, 50.dp, 150.dp, 150.dp, paint)
    }

    override fun onDraw(canvas: Canvas) {
        val count = canvas.saveLayer(0f, 0f, 150.dp, 150.dp, null)
        canvas.drawBitmap(destinationImage, 0f, 0f, paint)
        paint.xfermode = xfermode
        canvas.drawBitmap(sourceImage, 0f, 0f, paint)
        paint.xfermode = null
        canvas.restoreToCount(count)
    }
}

下边贴出来,这两个Demo表现出来的视频效果:

可以看到视频中各种模式错误和正确的示范效果,所以效果与上边流传的那张图为啥不一样的原因就在于我们:

忽略了透明这一个关键点,所以我们在使用时要注意了

不知大家发现没有,我代码里多了两行代码就是

 val count = canvas.saveLayer(0f, 0f, 150.dp, 150.dp, null)

 canvas.restoreToCount(count)

这两行是很关键的,这是离屏缓存的意思,就是canvas.sayLayer使接下来的绘制单独在屏幕外一块区域绘制,绘制之后利用canvas.restoreToCount 再绘制到屏幕上

如果不加离屏缓存的话,那Xfermode的计算也会算上该View下边的其他View的像素,接下来贴一下不加的话,会是怎么个效果:

所以还有一个关键点:

要使用离屏缓存进行绘制

总结一下,要注意两点:

1、相互叠加的两块区域要完全重合,没有内容的要利用透明像素填充,而不是不绘制

2、要使用离屏缓存,才可以排除掉当前绘制的View下边其他的像素

我的Demo传送门:XfermodeDemo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值