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