自定义View:Xfermode知识点解析

531 阅读3分钟

1、位图获取的优化

当有一张超大图,如何转化并得到实际需要的尺寸?

/**
 * 获取符合尺寸宽度的位图
 * @param width 位图的目标宽度
 */
fun getAvatar(width: Int): Bitmap {
    //获取options对象
    val options = BitmapFactory.Options()
    //配置中设置属性获取图片的长宽设置
    options.inJustDecodeBounds = true
    //对图片进行解码
    BitmapFactory.decodeResource(resources, R.drawable.girl, options)
    //取消获取图片的长宽的设置
    options.inJustDecodeBounds = false
    options.inDensity = options.outWidth   //实际宽度
    options.inTargetDensity = width   //目标宽度
    return BitmapFactory.decodeResource(resources, R.drawable.girl, options)
}

2、绘制一张位图

显示的位图大小

//位图的宽度
private val IMAGE_WIDTH = 200f.dp2px   //dp2px见自定义View第一篇文章
//画笔
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
//画位图
canvas.drawBitmap(getAvatar(IMAGE_WIDTH.toInt()),width/2- IMAGE_WIDTH/2,height/2-IMAGE_WIDTH/2,paint)

效果

image.png

3、从最基础的开始:如何得到一张圆角头像

通过一个例子引入Xfermode. 将上面的方形图设置为圆角图片:

//目标图片的宽度
private val IMAGE_WIDTH = 200f.dp2px
//画笔
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
//Xfermode的模式
private val SRC_IN = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)

在onSizeChanged方法中设定圆角图片位置(屏幕中心),因为onSizeChanged中能获取到width和height.

//圆角图片位置的RectF
private lateinit var bounds: RectF
//方法中获取bounds
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    super.onSizeChanged(w, h, oldw, oldh)
    bounds = RectF(
        width / 2 - IMAGE_WIDTH / 2,
        height / 2 - IMAGE_WIDTH / 2,
        width / 2 + IMAGE_WIDTH / 2,
        height / 2 + IMAGE_WIDTH / 2
    )
}

最后在onDraw方法中绘制

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    //saveLayer方法开销很大,一定要严格限制传入的bounds的大小,paint参数可以给null,并且需要拿到它的返回值
    val count = canvas.saveLayer(bounds, null)
    //画圆
    canvas.drawOval(
        bounds,
        paint
    )
    //设置xfermode
    paint.xfermode = SRC_IN
    //画位图
    canvas.drawBitmap(
        getAvatar(IMAGE_WIDTH.toInt()), //位图
        width / 2 - IMAGE_WIDTH / 2,   //左
        height / 2 - IMAGE_WIDTH / 2,  //上
        paint
    )
    //重置paint的xfermode模式
    paint.xfermode = null
    //saveLayer使用后需要重置,传入的参数就是saveLayer的返回值
    canvas.restoreToCount(count)
}


/**
 * 获取符合尺寸宽度的位图
 * @param width 位图的目标宽度
 */
fun getAvatar(width: Int): Bitmap {
    //获取options对象
    val options = BitmapFactory.Options()
    //配置中设置属性获取图片的长宽设置
    options.inJustDecodeBounds = true
    //对图片进行解码
    BitmapFactory.decodeResource(resources, R.drawable.girl, options)
    //取消获取图片的长宽的设置
    options.inJustDecodeBounds = false
    options.inDensity = options.outWidth   //实际宽度
    options.inTargetDensity = width   //目标宽度
    return BitmapFactory.decodeResource(resources, R.drawable.girl, options)
}

效果展示

image.png

4、Xfermode官方效果是如何实现的?

常见的错误的情况直接画圆和方,会发现结果与预期的不一致。

private lateinit var boundsOval: RectF
private lateinit var boundsRectF: RectF

//画笔
private val paintOval = Paint(Paint.ANTI_ALIAS_FLAG)
private val paintRectF = Paint(Paint.ANTI_ALIAS_FLAG)

init {
    paintOval.color = Color.parseColor("#D81B60")
    paintRectF.color = Color.parseColor("#2196F3")
}

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    super.onSizeChanged(w, h, oldw, oldh)
    boundsOval = RectF(
        width / 2f - IMAGE_WIDTH / 2,
        height / 2f - IMAGE_WIDTH / 2,
        width / 2f + IMAGE_WIDTH / 2,
        height / 2f + IMAGE_WIDTH / 2
    )

    boundsRectF = RectF(
        width / 2f - IMAGE_WIDTH,
        height / 2f,
        width / 2f,
        height / 2f + IMAGE_WIDTH
    )
}

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    val count = canvas.saveLayer(boundsOval, null)
    //画圆
    canvas.drawOval(
        boundsOval,
        paintOval
    )
    //给画方的paint设置xfermode
    paintRectF.xfermode = SRC_IN
    //画方
    canvas.drawRect(
        boundsRectF,
        paintRectF
    )
    paintOval.xfermode = null
    canvas.restoreToCount(count)
}

效果展示

image.png

而官方效果是

image.png

原因是bitmap 之间的混合才会生效,而不是简单的圆和方叠加。那么一个圆的Bitmap和一个方的Bitmap是否能达到官方的效果?答案也是否定。官方效果其实是如下实现的:

image.png

下面是代码实现

private lateinit var boundsLayer: RectF
private var circleBitmap: Bitmap
private var squareBitmap: Bitmap

//画笔
private val paintOval = Paint(Paint.ANTI_ALIAS_FLAG)
private val paintRectF = Paint(Paint.ANTI_ALIAS_FLAG)

init {
    paintOval.color = Color.parseColor("#D81B60")
    paintRectF.color = Color.parseColor("#2196F3")

    //创建圆和方的位图,大于圆和方的大小
    circleBitmap =
        Bitmap.createBitmap(
            (IMAGE_WIDTH / 2 * 3).toInt(),
            (IMAGE_WIDTH / 2 * 3).toInt(),
            Bitmap.Config.ARGB_8888
        )
    squareBitmap =
        Bitmap.createBitmap(
            (IMAGE_WIDTH / 2 * 3).toInt(),
            (IMAGE_WIDTH / 2 * 3).toInt(),
            Bitmap.Config.ARGB_8888
        )
}

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    super.onSizeChanged(w, h, oldw, oldh)
    boundsLayer = RectF(
        width / 2f - IMAGE_WIDTH,
        height / 2f - IMAGE_WIDTH / 2,
        width / 2f + IMAGE_WIDTH / 2,
        height / 2f + IMAGE_WIDTH
    )

    //将圆和方画在画布Bitmap上
    val canvas = Canvas(circleBitmap)
    canvas.drawOval(IMAGE_WIDTH / 2, 0f, IMAGE_WIDTH / 2 * 3, IMAGE_WIDTH, paintOval)
    canvas.setBitmap(squareBitmap)
    canvas.drawRect(0f, IMAGE_WIDTH / 2, IMAGE_WIDTH, IMAGE_WIDTH / 2 * 3, paintRectF)
}

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    val count = canvas.saveLayer(boundsLayer, null)
    //画圆的Bitmap
    canvas.drawBitmap(
        circleBitmap,
        width / 2f- IMAGE_WIDTH ,
        height / 2f- IMAGE_WIDTH/2 ,
        paintOval
    )
    //给画方的paint设置xfermode
    paintRectF.xfermode = ADD  //ADD = PorterDuffXfermode(PorterDuff.Mode.ADD)
    //画方的Bitmap
    canvas.drawBitmap(
        squareBitmap,
        width / 2f- IMAGE_WIDTH ,  //二个Bitmap绘制的起始位置相同
        height / 2f- IMAGE_WIDTH/2 ,
        paintRectF
    )
    paintOval.xfermode = null
    canvas.restoreToCount(count)
}

效果展示:

image.png

5、xfermode的模式和相关知识点解析

xfermode有十六种模式(基于Android API 29):

/**
 * @hide
 */
public static Mode intToMode(int val) {
    switch (val) {
        default:
        case  0: return Mode.CLEAR;
        case  1: return Mode.SRC;
        case  2: return Mode.DST;
        case  3: return Mode.SRC_OVER;
        case  4: return Mode.DST_OVER;
        case  5: return Mode.SRC_IN;
        case  6: return Mode.DST_IN;
        case  7: return Mode.SRC_OUT;
        case  8: return Mode.DST_OUT;
        case  9: return Mode.SRC_ATOP;
        case 10: return Mode.DST_ATOP;
        case 11: return Mode.XOR;
        case 16: return Mode.DARKEN;
        case 17: return Mode.LIGHTEN;
        case 13: return Mode.MULTIPLY;
        case 14: return Mode.SCREEN;
        case 12: return Mode.ADD;
        case 15: return Mode.OVERLAY;
    }
}

相关效果网上很多,不再展示。xfermode的效果是色值和透明度的叠加效果。

--个人学习笔记--