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)
效果
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)
}
效果展示
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)
}
效果展示
而官方效果是
原因是bitmap 之间的混合才会生效,而不是简单的圆和方叠加。那么一个圆的Bitmap和一个方的Bitmap是否能达到官方的效果?答案也是否定。官方效果其实是如下实现的:
下面是代码实现
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)
}
效果展示:
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的效果是色值和透明度的叠加效果。
--个人学习笔记--