Canvas - 使用技巧

Canvas

  1. 概念:画布,通过画笔绘制几何图形、文本、路径和位图等
  2. 常用API类型:绘制、变换、状态保存和恢复

绘制几何图形、文本、路径和位图等API

// 在指定坐标绘制位图。
void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) 
// 根据给定的起始点和结束点之间绘制连线。
void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) 
// 根据给定的路径,绘制连线。
void drawPath(Path path, Paint paint)
// 根据给定的坐标,绘制点。
void drawPoint(float x, float y, Paint paint)
// 根据给定的坐标,绘制文字。
void drawText(String text, int start, int end, float x, float y, Paint paint)
...

位置、形状变换等API

// 平移
void translate(float dx, float dy)
// 缩放
void scale(float sx, float sy)
// 旋转
void rotate(float degrees)
// 倾斜
void skew(float sx, float sy)
// 切割操作,参数指定区域内可以继续绘制
void clipXXX(...)
// 反向切割操作,参数指定区域内不可以绘制
void clipOutXXX(...)
// 可通过Matrix实现平移、缩放、旋转等操作
void setMatrix(Matrix matrix)
...

Canvas平移演示

  1. 在(0,0)绘制一个400x400的红色矩形

    mPaint.setColor(Color.RED);
    canvas.drawRect(0, 0, 400, 400, mPaint);
    
  2. Canvas平移到(50,50),再绘制一个400x400的蓝色矩形

    canvas.translate(50,50);
    mPaint.setColor(Color.BLUE);
    canvas.drawRect(0, 0, 400, 400, mPaint);
    
  3. 再绘制一个起点为(0,0)到终点(600,600)的绿线

    mPaint.setColor(Color.GREEN);
    canvas.drawLine(0,0,600,600,mPaint);
    

Canvas_translate

由此可以看出,Canvas由(0,0)向x,y轴正方向各平移了50后,坐标系原点到了之前坐标系的(50,50)位置,坐标系已经改变了。

Canvas缩放演示

  1. 先画一个起点在(300,300)边长为300的红色矩形,然后将Canvas缩放0.5倍,再画一个起点(300,300)边长为300的蓝色矩形。
    mPaint.setColor(Color.RED);
    canvas.drawRect(300, 300, 600, 600, mPaint);
    canvas.scale(0.5f, 0.5f);
    mPaint.setColor(Color.BLUE);
    canvas.drawRect(300, 300, 600, 600, mPaint);
    
  2. 也是先画一个起点在(300,300)边长为300的红色矩形,然后在(300,300)这点进行Canvas缩放0.5倍,再画一个起点(300,300)边长为300的蓝色矩形。
    mPaint.setColor(Color.RED);
    canvas.drawRect(300, 300, 600, 600, mPaint);
    canvas.scale(0.5f, 0.5f, 300, 300);
    mPaint.setColor(Color.BLUE);
    canvas.drawRect(300, 300, 600, 600, mPaint);
    

canvas_scale

可以看出Canvas缩放后,坐标系也跟着变化了,而canvas.scale(0.5f, 0.5f, 300, 300)这个方法的原理是缩放前先平移到(300,300),缩放后坐标系改变了再反向平移(300,300),这样能保证后绘制的蓝色矩形和红色矩形是同起点。

public final void scale(float sx, float sy, float px, float py) {
    if (sx == 1.0f && sy == 1.0f) return;
    translate(px, py);
    scale(sx, sy);
    translate(-px, -py);
}

源码如此,我们也可以来验证一下:

mPaint.setColor(Color.RED);
canvas.drawRect(300, 300, 600, 600, mPaint);
canvas.translate(300, 300);
canvas.scale(0.5f, 0.5f);
canvas.translate(-300, -300);
mPaint.setColor(Color.BLUE);
canvas.drawRect(300, 300, 600, 600, mPaint);
// 绘制新坐标系
mPaint.setColor(Color.GREEN);
canvas.drawLine(0,0,600,0,mPaint);
canvas.drawLine(0,0,0,600,mPaint);

Canvas_scale2

Canvas旋转演示

  1. 先绘制一个起点在(300,300)边长为300的红色矩形,Canvas顺时针旋转15°,再绘制一个起点在(300,300)边长为300的蓝色矩形。
    mPaint.setColor(Color.RED);
    canvas.drawRect(300, 300, 600, 600, mPaint);
    canvas.rotate(15);
    mPaint.setColor(Color.BLUE);
    canvas.drawRect(300, 300, 600, 600, mPaint);
    
  2. 同理,改变Canvas绕(300,300)顺时针旋转为15°,再再绘制一个起点在(300,300)边长为300的蓝色矩形。
    mPaint.setColor(Color.RED);
    canvas.drawRect(300, 300, 600, 600, mPaint);
    canvas.rotate(15, 300, 300); //px,py表示旋转中心
    mPaint.setColor(Color.BLUE);
    canvas.drawRect(300, 300, 600, 600, mPaint);
    

Canvas_rotate
可以看出,两张图都发生了旋转,但是旋转中心不同,而且两个Canvas坐标系都发生了15°旋转。

Canvas错切(倾斜)演示

错切:在某方向上,按照一定的比例对图形的每个点到某条平行于该方向的直线的有向距离做缩放得到的平面图形。

在平面上,水平错切(或平行于X轴的错切)是一个将任一点(x, y)映射到点(x+my, y)的操作,m是固定参数,称为错切因子。同理,垂直错切就是将水平错切的x, y互换。

说得再土一点,水平错切就是在水平方向上“悠”,垂直错切就是在垂直方向上“悠”。

mPaint.setColor(Color.RED);
canvas.drawRect(0, 0, 300, 300, mPaint);
// canvas.skew(0.577f,0); // 倾角60° cot60°=0.577
// canvas.skew(1,0); // 倾角45° cot45°=1
canvas.skew(1.732f,0); // 倾角30° cot30°=1.732
mPaint.setColor(Color.BLUE);
canvas.drawRect(0, 0, 300, 300, mPaint);

Canvas_skew1

mPaint.setColor(Color.RED);
canvas.drawRect(0, 0, 300, 300, mPaint);
// canvas.skew(0, 0.577f); 
// canvas.skew(0, 1); 
canvas.skew(0, 1.732f); 
mPaint.setColor(Color.BLUE);
canvas.drawRect(0, 0, 300, 300, mPaint);

Canvas_skew2

Canvas裁剪演示

mPaint.setColor(Color.RED);
canvas.drawRect(300, 300, 600, 600, mPaint);
canvas.clipRect(300, 300, 600, 600); // 画布被裁剪
mPaint.setColor(Color.BLUE);
canvas.drawRect(350, 350, 650, 650, mPaint); // 超出画布区域的无法绘制,画布内的可以正常绘制

Canvas_clip

clipOut反向裁剪,表现在下面代码中就是——红框内区域不属于画布,只显示画布内的区域。

mPaint.setColor(Color.RED);
canvas.drawRect(300, 300, 600, 600, mPaint);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    canvas.clipOutRect(300, 300, 600, 600); // 画布被裁剪
}
mPaint.setColor(Color.BLUE);
canvas.drawRect(350, 350, 650, 650, mPaint); // 超出画布区域的无法绘制,画布内的可以正常绘制

Canvas_clipout

Canvas设置矩阵演示

Canvas也可以通过设置矩阵的方式,来达到平移、缩放、旋转、倾斜等操作。

mPaint.setColor(Color.RED);
canvas.drawRect(300, 300, 600, 600, mPaint);
Matrix matrix = new Matrix();
matrix.setTranslate(50, 50);
// matrix.setScale(0.5f, 0.5f);
// matrix.setRotate(45, 450, 450);
// matrix.setSkew(0.577f, 0);
canvas.setMatrix(matrix);
mPaint.setColor(Color.BLUE);
canvas.drawRect(300, 300, 600, 600, mPaint);

效果图就不用贴了,和上面的道理是一样的,都是Canvas发生了变化,导致Canvas上的图形发生了变化。

那么,有没有图形发生变化后,Canvas恢复到原来的状态呢?这样既得到想要的图形,又不影响下一次的绘制。

Canvas状态保存和恢复

Canvas再调用了translate、scale、rotate、skew、clipRect等变换后,后续的操作都是基于变换后的Canvas,都会受到影响,对于后续的操作很不方便。Canvas提供了save、saveLayer、saveLayerAlpha、restore、restoreToCount来保存和恢复状态。这些状态保存在栈里,恢复就是从栈里取出。

restore等方法必须在调用了save等方法后,才能使用,否则会报错。 为什么会报错呢?很简单,栈里没有保存的状态,取出操作当然会报错啊。

int state = canvas.save(); // 保存状态1
Log.e("TAG", "onDraw: " + canvas.getSaveCount());
canvas.translate(70, 50);
canvas.drawRect(0, 0, 400, 400, mPaint);
canvas.save(); // 保存状态2
canvas.restore(); // 恢复最新状态(状态2)
Log.e("TAG", "onDraw: " + canvas.getSaveCount());
mPaint.setColor(Color.BLUE);
canvas.drawRect(0, 0, 400, 400, mPaint);
canvas.restoreToCount(state); // 手动指定的恢复到状态1
发布了19 篇原创文章 · 获赞 0 · 访问量 852

猜你喜欢

转载自blog.csdn.net/qq_32036981/article/details/103920372