Canvas 的范围裁切
- clipRect()
- clipPath() clipPath() 切出来的圆为什么没有抗锯⻮效果?因为「强行切边」
- clipOutRect() / clipOutPath()
如下:
- 首先我们绘制一个矩形
- 然后用clipPath去裁切一个圆形
- 最后用clipRect去裁切一个矩形,最后得到了一个扇形
Canvas 的几何变换
- translate(x, y) 移动
canvas.save(); canvas.translate(200.dp, 0.dp); canvas.drawBitmap(bitmap, x, y, paint); canvas.restore(); - rotate(degree) 旋转
//旋转 canvas.rotate(45f, 100.dp, 100.dp); -
- scale(x, y) 放缩
//等比放大1.3倍 //canvas.scale(1.3f, 1.3f, BITMAP_PADDING + BITMAP_SIZE / 2, BITMAP_PADDING + BITMAP_SIZE / 2); //不等比放大 canvas.scale(1.3f, 0.7f, BITMAP_PADDING + BITMAP_SIZE / 2, BITMAP_PADDING + BITMAP_SIZE / 2); - skew(x, y)
//参数里的 sx 和 sy 是 x 方向和 y 方向的错切系数。 canvas.skew(0f, 0.5f); -
重点:Canvas 的几何变换方法参照的是 View 的坐标系,而绘制方法 (drawXxx())参照的是 Canvas 自己的坐标系。
关于多重变换:
Canvas 的变换方法多次调用的时候,由于 Canvas 的坐标系会整体被变换,因此当 平移、旋转、放缩、错切等变换多重存在的时候,Canvas 的变换参数会非常难以计 算,因此可以改用倒序的理解方式:
将 Canvas 的变换理解为 Canvas 的坐标系不变,每次变换是只对内部的绘制内 容进行变换,同时把 Canvas 的变换顺序看作是倒序的(即写在下面的变换先 执行),可以更加方便进行多重变换的参数计算。
Matrix 的几何变换
Camera投影效果
private val camera = Camera()
init{
camera.rotateX(30f)
}
onDraw{
//投影效果
canvas.translate(BITMAP_PADDING+ BITMAP_SIZE/2,BITMAP_PADDING+ BITMAP_SIZE/2);
camera.applyToCanvas(canvas)
canvas.translate(-(BITMAP_PADDING+ BITMAP_SIZE/2),-(BITMAP_PADDING+ BITMAP_SIZE/2))
canvas.drawBitmap(bitmap, BITMAP_PADDING, BITMAP_PADDING,paint)
}
翻页效果
onDraw{
//翻页效果
//上半部分 反着看,先裁切,再移动
//移动
canvas.save()
canvas.translate(BITMAP_PADDING+ BITMAP_SIZE/2,BITMAP_PADDING+ BITMAP_SIZE/2);
//裁切
canvas.clipRect(-BITMAP_SIZE/2, -BITMAP_SIZE/2, BITMAP_SIZE/2, BITMAP_SIZE/2)
canvas.translate(-(BITMAP_PADDING+ BITMAP_SIZE/2),-(BITMAP_PADDING+ BITMAP_SIZE/2))
canvas.drawBitmap(bitmap, BITMAP_PADDING, BITMAP_PADDING,paint)
canvas.restore()
//画一条线
canvas.save()
paint.color = Color.parseColor("#FF0000")
canvas.drawLine(BITMAP_SIZE/2,BITMAP_PADDING+ BITMAP_SIZE/2, BITMAP_PADDING+BITMAP_SIZE,BITMAP_PADDING+ BITMAP_SIZE/2,paint)
canvas.restore()
//下半部分,反着看,先裁切,camera旋转,再移动
//移动
canvas.save()
canvas.translate(BITMAP_PADDING+ BITMAP_SIZE/2,BITMAP_PADDING+ BITMAP_SIZE/2);
camera.applyToCanvas(canvas)
//裁切
canvas.clipRect(-BITMAP_SIZE/2, 2f, BITMAP_SIZE/2, BITMAP_SIZE/2)
canvas.translate(-(BITMAP_PADDING+ BITMAP_SIZE/2),-(BITMAP_PADDING+ BITMAP_SIZE/2))
canvas.drawBitmap(bitmap, BITMAP_PADDING, BITMAP_PADDING,paint)
canvas.restore()
}
完整代码
private val BITMAP_SIZE = 200.dp
private val BITMAP_PADDING = 100.dp
class CameraView (context: Context,attributeSet: AttributeSet): View(context,attributeSet) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val bitmap = getAvatar(BITMAP_SIZE.toInt())
@RequiresApi(VERSION_CODES.LOLLIPOP)
private val clipped = Path().apply {
addOval(BITMAP_PADDING, BITMAP_PADDING, BITMAP_PADDING+ BITMAP_SIZE, BITMAP_PADDING+ BITMAP_SIZE,CCW)
}
private val camera = Camera()
init {
camera.rotateX(30f)
camera.setLocation(0f,0f,-3*resources.displayMetrics.density)
}
@RequiresApi(VERSION_CODES.LOLLIPOP)
override fun onDraw(canvas: Canvas) {
//1. Canvas范围裁切
// //椭圆裁切
// canvas.clipPath(clipped)
// //矩形裁切,左上角切四分之一
// canvas.clipRect(BITMAP_PADDING, BITMAP_PADDING, BITMAP_PADDING+ BITMAP_SIZE/2, BITMAP_PADDING+ BITMAP_SIZE/2)
// canvas.drawBitmap(bitmap, BITMAP_PADDING,BITMAP_PADDING,paint)
//2. Canvas几何变换
// canvas.save();
//平移
// canvas.translate(200.dp, 0.dp);
//旋转
// canvas.rotate(45f, 100.dp, 100.dp);
//等比放大1.3倍
// canvas.scale(1.3f, 1.3f, BITMAP_PADDING + BITMAP_SIZE / 2, BITMAP_PADDING + BITMAP_SIZE / 2);
//不等比放大
// canvas.scale(1.3f, 0.7f, BITMAP_PADDING + BITMAP_SIZE / 2, BITMAP_PADDING + BITMAP_SIZE / 2);
//参数里的 sx 和 sy 是 x 方向和 y 方向的错切系数。
// canvas.skew(0f, 0.5f);
// canvas.drawBitmap(bitmap, BITMAP_PADDING, BITMAP_PADDING, paint);
// canvas.restore();
//3. Matrix 几何变换
//投影效果
// canvas.translate(BITMAP_PADDING+ BITMAP_SIZE/2,BITMAP_PADDING+ BITMAP_SIZE/2);
// camera.applyToCanvas(canvas)
// canvas.translate(-(BITMAP_PADDING+ BITMAP_SIZE/2),-(BITMAP_PADDING+ BITMAP_SIZE/2))
// canvas.drawBitmap(bitmap, BITMAP_PADDING, BITMAP_PADDING,paint)
//翻页效果
//上半部分 反着看,先裁切,再移动
//移动
canvas.save()
canvas.translate(BITMAP_PADDING+ BITMAP_SIZE/2,BITMAP_PADDING+ BITMAP_SIZE/2);
//裁切
canvas.clipRect(-BITMAP_SIZE/2, -BITMAP_SIZE/2, BITMAP_SIZE/2, BITMAP_SIZE/2)
canvas.translate(-(BITMAP_PADDING+ BITMAP_SIZE/2),-(BITMAP_PADDING+ BITMAP_SIZE/2))
canvas.drawBitmap(bitmap, BITMAP_PADDING, BITMAP_PADDING,paint)
canvas.restore()
//画一条线
canvas.save()
paint.color = Color.parseColor("#FF0000")
canvas.drawLine(BITMAP_SIZE/2,BITMAP_PADDING+ BITMAP_SIZE/2, BITMAP_PADDING+BITMAP_SIZE,BITMAP_PADDING+ BITMAP_SIZE/2,paint)
canvas.restore()
//下半部分,反着看,先裁切,camera旋转,再移动
//移动
canvas.save()
canvas.translate(BITMAP_PADDING+ BITMAP_SIZE/2,BITMAP_PADDING+ BITMAP_SIZE/2);
camera.applyToCanvas(canvas)
//裁切
canvas.clipRect(-BITMAP_SIZE/2, 2f, BITMAP_SIZE/2, BITMAP_SIZE/2)
canvas.translate(-(BITMAP_PADDING+ BITMAP_SIZE/2),-(BITMAP_PADDING+ BITMAP_SIZE/2))
canvas.drawBitmap(bitmap, BITMAP_PADDING, BITMAP_PADDING,paint)
canvas.restore()
}
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)
}
}