通过canvas的线性操作与裁剪操作加深对canvas坐标系的理解
Canvas坐标系
canvas坐标系如图所示,以view的左上角为原点,向右为X轴正方向,向下为Y轴正方向
数学坐标系上以X轴正方向为起点逆时针旋转为正角度,顺时针旋转为负角度。与数学坐标系不同的是:canvas坐标系以X轴正方向为起点顺时针旋转为正角度,逆时针为负角度
Canvas的线性操作
canvas的线性操作是可以叠加的,可以看做是对canvas的坐标系的操作,canvas本身并不会受到影响,canvas原本长什么样操作完还是什么样
-
平移(translate)
基于坐标系当前的位置移动来坐标系
override fun onDraw(canvas: Canvas) { //1.将canvas坐标系移动(100,100),并绘制圆 canvas.translate(100f, 100f) canvas.drawCircle(0f, 0f, 50f, paint) //2.在1的基础上将坐标系移动(100,100),并绘制圆,此时坐标系的原点在(200,200) canvas.translate(100f, 100f) canvas.drawCircle(0f, 0f, 50f, paint) }
-
旋转(rotate)
基于指定的中心点或者坐标原点旋转坐标系
override fun onDraw(canvas: Canvas) { //先平移到canvas的中心点 canvas.translate(width/2f,height/2f) //1.画一条线 paint.color = Color.BLACK canvas.drawLine(-200f,0f,200f,0f,paint) //2.将坐标系旋转30°(后面两个参数用于指定旋转中心),画一条线 canvas.rotate(30f,0f,0f) paint.color = Color.RED canvas.drawLine(-200f,0f,200f,0f,paint) //3.在2的基础上再旋转30°(无指定的旋转中心,则以坐标原点为中心),画一条线 canvas.rotate(30f) paint.color = Color.BLUE canvas.drawLine(-200f,0f,200f,0f,paint) }
-
缩放(scale)
基于指定的中心点或者坐标原点以系数(sx,sy)缩放坐标系x轴和y轴
缩放系数取值范围对应的行为如下:
取值范围 行为 (-∞, -1) 放大对应系数后,根据中心轴翻转(例:中心点为原点时 sx对应的中心轴为x轴,sy对应y轴) -1 以x轴或y轴为轴心翻转 (-1, 0) 缩小对应系数后,根据中心轴翻转 0 sx为0宽度不显示,sy为0高度不显示 (0, 1) 缩小对应系数 1 不变 (1, +∞) 放大对应系数 private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = Color.BLACK style = Paint.Style.STROKE strokeWidth = 10f } private val rect = RectF(-50f,-50f,50f,50f) override fun onDraw(canvas: Canvas) { //先平移到canvas的中心点 canvas.translate(width/2f,height/2f) //画一个矩形 canvas.drawRect(rect,paint) //坐标系放大2倍后画一个矩形 canvas.scale(2f,2f) paint.color = Color.RED canvas.drawRect(rect,paint) //坐标系再放大2倍后画一个矩形 canvas.scale(2f,2f) paint.color = Color.BLUE canvas.drawRect(rect,paint) }
-
错切(skew)
特殊的线性操作,对x或y坐标进行变换
api:
public void skew(float sx, float sy)变换后的坐标:
- x = x + sx * y
- y = y + sy * x
private val rect = RectF(-100f, -100f, 100f, 100f) override fun onDraw(canvas: Canvas) { //先平移到canvas的中心点 canvas.translate(width / 2f, height / 2f) //画一个正常的矩形 canvas.drawRect(rect,paint) canvas.save() //向上移动600 canvas.translate(0f,-600f) //错切y canvas.skew(0f,1f) //画矩形 canvas.drawRect(rect,paint) canvas.restore() canvas.save() //向下移动600 canvas.translate(0f,600f) //错切x canvas.skew(1f,0f) //画矩形 canvas.drawRect(rect,paint) canvas.restore() }
Canvas的裁剪操作
canvas的裁剪操作api分为以下四类,按字面意思很好理解就不做演示了~
canvas.clipRect(...)//根据rect裁剪出指定的范围
canvas.clipPath(...)//根据path裁剪出指定的范围
canvas.clipOutPath(...)//根据rect裁剪出指定区域以外的范围
canvas.clipOutRect(...)//根据path裁剪出指定区域以外的范围
Canvas坐标系变化对Canvas的影响
下面的代码结合线性与裁剪操作来展示坐标系的变化对canvans本身的影响
private val canvasColor = 0x33000000
private val clipRect = RectF(-200f, -200f, 200f, 200f)
private val bitmapPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val bitmap: Bitmap
init {
//加载一张大小为600*600的bitmap
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeResource(resources, R.drawable.ic_avatar, options)
options.inJustDecodeBounds = false
options.inDensity = options.outWidth
options.inTargetDensity = 600
bitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_avatar, options)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.withSave {
//将坐标系移动到中心
canvas.translate(width / 2f, height / 2f)
//将坐标系旋转60°
canvas.rotate(60f)
//将坐标系放大1.5倍
canvas.scale(1.5f, 1.5f)
//裁剪canvas
canvas.clipRect(clipRect)
//为裁剪后的canvas上色
canvas.drawColor(canvasColor)
//将坐标系旋转-60°
canvas.rotate(-60f)
//将坐标系缩小至原本大小
canvas.scale(1 / 1.5f, 1 / 1.5f)
//绘制图片
canvas.drawBitmap(bitmap, -300f, -300f, bitmapPaint)
}
}
对应的坐标系变化以及canvas变化如下方的Gif所示(演示了过程,并不是上述代码的实际运行效果)
canvas的线性操作影响的是坐标系,对canvas进行裁剪或是绘制行为,是基于坐标系在canvas上的映射,坐标系的移动并不会导致canvas发生变化。canvas裁剪或者绘制完成后,无论怎么进行旋转,平移,缩放,被裁剪出来的区域或是已绘制的内容不会发生变化
注:
- 白色方框代表即将裁剪的区域
- 灰色代表着canvas,中心的小灰块代表着canvas裁剪出来的区域
- 每个刻度长度为100