canvas的线性操作与坐标系

941 阅读4分钟

通过canvas的线性操作与裁剪操作加深对canvas坐标系的理解

Canvas坐标系

canvas坐标系如图所示,以view的左上角为原点,向右为X轴正方向,向下为Y轴正方向

canvas坐标系.png

数学坐标系上以X轴正方向为起点逆时针旋转为正角度,顺时针旋转为负角度。与数学坐标系不同的是:canvas坐标系以X轴正方向为起点顺时针旋转为正角度,逆时针为负角度

坐标系对比.png

Canvas的线性操作

canvas的线性操作是可以叠加的,可以看做是对canvas的坐标系的操作,canvas本身并不会受到影响,canvas原本长什么样操作完还是什么样

  1. 平移(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)
        }
    

translate.png

  1. 旋转(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)
        }
    

rotate.png

  1. 缩放(scale)

    基于指定的中心点或者坐标原点以系数(sx,sy)缩放坐标系x轴和y轴

    缩放系数取值范围对应的行为如下:

    取值范围行为
    (-∞, -1)放大对应系数后,根据中心轴翻转(例:中心点为原点时 sx对应的中心轴为x轴,sy对应y轴)
    -1以x轴或y轴为轴心翻转
    (-1, 0)缩小对应系数后,根据中心轴翻转
    0sx为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)
        }
    

scale.png

  1. 错切(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()
        }
    

skew.png

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裁剪或者绘制完成后,无论怎么进行旋转,平移,缩放,被裁剪出来的区域或是已绘制的内容不会发生变化

注:

  1. 白色方框代表即将裁剪的区域
  2. 灰色代表着canvas,中心的小灰块代表着canvas裁剪出来的区域
  3. 每个刻度长度为100

demo_canvas.gif