为什么要用 Xfermode?
为了把多次绘制进行「合成」,例如蒙版效果:用 A 的形状和 B 的图案
怎么做
- Canvs.saveLayer() 把绘制区域拉到单独的离屏缓冲里
- 绘制 A 图形
- 用 Paint.setXfermode() 设置 Xfermode
- 绘制 B 图形
- 用 Paint.setXfermode(null) 恢复 Xfermode
- 用 Canvas.restoreToCount() 把离屏缓冲中的合成后的图形放回绘制区域
为什么要用 saveLayer() 才能正确绘制 ?
为了把需要互相作用的图形放在单独的位置来绘制,不会受 View 本身的影响。 如果不使用 saveLayer(),绘制的目标区域将总是整个 View 的范围,两个图形 的交叉区域就错误了
画一个圆形头像
我们可以先画一个方形的头像,像这样
canvas.drawBitmap(getAvatar(IMAGE_WIDTH.toInt()),IMAGE_PADDING,IMAGE_PADDING,paint)
为了能够画出一个圆角头像,我们需要使用Xfermode
- 首先我们要画一个圆形(源图形),直径跟方形头像边长一致,它是透明
- 接下来使用
SRC_IN设置paint的Xfermode - 然后画一个方形头像(目标图形)
- 现在我们知道,头像的四个角并没有被实心原型盖住,对于SRC_IN来讲,圆形在方形头像中的展示,那么就会展示出一个圆形,这四个角是透明的,所以不会被绘制出来,那么最后的效果就是圆形头像了
完整代码如下
private val IMAGE_WIDTH = 200f.px
private val IMAGE_PADDING = 100f.px
private val XFERMODE = PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
class AvatarView(context: Context,attributeSet: AttributeSet): View(context,attributeSet) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG);
private val bounds = RectF(IMAGE_PADDING,IMAGE_PADDING,IMAGE_PADDING+ IMAGE_WIDTH,IMAGE_PADDING+IMAGE_WIDTH)
@RequiresApi(VERSION_CODES.LOLLIPOP)
override fun onDraw(canvas: Canvas) {
val count = canvas.saveLayer(bounds,null);
canvas.drawOval(IMAGE_PADDING,IMAGE_PADDING,IMAGE_PADDING+ IMAGE_WIDTH,IMAGE_PADDING+IMAGE_WIDTH,paint)
paint.xfermode = XFERMODE
canvas.drawBitmap(getAvatar(IMAGE_WIDTH.toInt()),IMAGE_PADDING,IMAGE_PADDING,paint)
paint.xfermode = null
canvas.restoreToCount(count)
}
fun getAvatar(width: Int): Bitmap {
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeResource(resources, R.mipmap.slmh, options)
options.inJustDecodeBounds = false
options.inDensity = options.outWidth
options.inTargetDensity = width
return BitmapFactory.decodeResource(resources, R.mipmap.slmh, options)
}
}
XfermodeView的其他效果
- 先画一个目标图形红色圆形,使用canvas.drawOval的方式
- 再画一个源图形蓝色方形,使用canvas.drawRect的方式
- 绘制的时候变换Xfermode,看下效果
完整代码如下
private val XFERMODE = PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
class XfermodeView(context: Context,attributeSet: AttributeSet): View(context,attributeSet) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG);
private val bounds = RectF(50f.px,50f.px,300f.px,200f.px)
private val circleBitmap = Bitmap.createBitmap(150f.px.toInt(),150f.px.toInt(),Bitmap.Config.ARGB_8888)
private val squareBitmap = Bitmap.createBitmap(150f.px.toInt(),150f.px.toInt(),Bitmap.Config.ARGB_8888)
init {
val canvas = Canvas(circleBitmap)
paint.color = Color.parseColor("#D81B60")
//1 目标图形红色圆形
canvas.drawOval(50f.px, 0f.px, 150f.px, 100f.px, paint)
paint.color = Color.parseColor("#2196F3")
//2 源图形蓝色方形
canvas.setBitmap(squareBitmap)
canvas.drawRect(0f.px, 50f.px, 100f.px, 150f.px, paint)
}
@RequiresApi(VERSION_CODES.LOLLIPOP)
override fun onDraw(canvas: Canvas) {
//3 绘制的时候变换xFermode
val count = canvas.saveLayer(bounds,null);
canvas.drawBitmap(circleBitmap,150f.px,50f.px,paint)
paint.xfermode = XFERMODE
canvas.drawBitmap(squareBitmap,150f.px,50f.px,paint)
paint.xfermode = null
canvas.restoreToCount(count)
}
这里SRC_IN 也就是Source In 字面意思可以理解为源图片蓝色方形在目标图片红色原型中的展示,所以最后效果是一个蓝色90度扇形
更多效果可以参照下图理解
官方文档链接