Flutter学习:使用CustomPaint绘制图形

1,397

Flutter学习:认识CustomPaint组件和Paint对象

Flutter学习:使用CustomPaint绘制路径

Flutter学习:使用CustomPaint绘制图形

Flutter学习:使用CustomPaint绘制文字

Flutter学习:使用CustomPaint绘制图片

Canvas

canvas绘制的所有内容都是以屏幕左上角为原点,右边和下边为正方向延伸的直角坐标系。

image

drawCircle 绘制圆形

drawCircle需要传递3个参数:

  • Offset c:圆心的位置坐标
  • double radius:圆的半径
  • Paint paint:绘制对象
// 以左上角为圆心绘制
canvas.drawCircle(const Offset(0, 0), 100, paint);
// 以中心点为圆心绘制
canvas.drawCircle(size.center(Offset.zero), 100, paint);
// 以右下角为圆心绘制
canvas.drawCircle(Offset(size.width, size.height), 100, paint);![CustomPaint_drawCircle]

image

drawRect 绘制矩形

drawCircle需要传递2个参数:

  • Rect rect:Rect对象
  • Paint paint:绘制对象

创建Rect对象的方法有7种:

Rect.zero

绘制一个左、上、右和下边缘都为零的矩形

Rect rect = Rect.zero;
canvas.drawRect(rect, paint);

Rect.largest

绘制一个覆盖整个坐标空间的矩形

Rect rect = Rect.largest;
canvas.drawRect(rect, paint);

image

Rect.fromCenter

确定一个矩形的中心点坐标来绘制。

Rect.fromCenter({ required Offset center, required double width, required double height})需要传递3个参数:

  • center用来设置矩形的中心点坐标
  • width用来设置矩形的宽
  • height用来确定矩形的高
Offset center = size.center(Offset.zero);
Rect rect = Rect.fromCenter(center: center, width: 250, height: 250);
canvas.drawRect(rect, paint);

image

Rect.fromLTRB

从左、上、右和下边缘构造一个矩形

Rect.fromLTRB( double left, double top, double right, double bottom)需要传递4个参数:

  • left代表左上角顶点的x坐标
  • top代表左上角顶点的y坐标
  • right代表右下角顶点的x坐标
  • bottom代表右下角顶点的y坐标
Rect rect = const Rect.fromLTRB(50, 300, 350, 500);
canvas.drawRect(rect, paint);

image

Rect.fromCircle

构造一个以给定圆为边界的矩形

Rect.fromCircle({required Offset center, required double radius})需要传递2个对象:

  • center用来设置圆的圆心左标
  • radius用来设置圆的半径
Offset center = size.center(Offset.zero);
Rect rect = Rect.fromCircle(center: center, radius: size.width / 3);
canvas.drawRect(rect, paint);

image

Rect.fromLTWH

通过左上角点的坐标和宽度、高度来构造一个矩形

Rect.fromLTWH( double left, double top, double width, double height)需要传递4个参数:

  • left用来设置左上角的x坐标
  • top用来设置左上角的y坐标
  • width用来设置矩形的宽
  • height用来设置矩形的高
Rect rect = const Rect.fromLTWH(50, 220, 300, 300);
canvas.drawRect(rect, paint);

image

Rect.fromPoints

通过两个坐标点来绘制矩形。

Rect.fromPoints(Offset a, Offset b)需要传递两个Offset对象:

  • a代表左上角点的坐标
  • b代表右下角的点的坐标
Rect rect = Rect.fromPoints(const Offset(60, 200), const Offset(320, 500));
canvas.drawRect(rect, paint);

image

drawRRect 绘制圆角矩形

RRect.zero

绘制一个左、上、右和下边缘都为零的圆角矩形,和Rect.zero效果一样

RRect rrect = RRect.zero;

RRect.fromRectAndRadius

通过绘制一个矩形再设置圆角半径来绘制圆角矩形

RRect RRect.fromRectAndRadius(Rect rect, Radius radius)需要传递2个参数:

  • rect是一个矩形对象
  • radius为矩形设置圆角
Rect rect = Rect.fromPoints(const Offset(50, 200), const Offset(320, 600));
RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(40.0));
canvas.drawRRect(rrect, paint);

image

RRect.fromLTRBR

RRect.fromLTRBR(double left, double top, double right, double bottom, Radius radius)需要传递5个参数,前4个参数和Rect.fromLTRB一样:

  • left代表左上角顶点的x坐标
  • top代表左上角顶点的y坐标
  • right代表右下角顶点的x坐标
  • bottom代表右下角顶点的y坐标
  • radius用来设置矩形的圆角半径
RRect rrect = RRect.fromLTRBR(80, 120, 320, 420, const Radius.circular(48));
canvas.drawRRect(rrect, paint);

image

RRect.fromLTRBXY

RRect.fromLTRBXY( double left, double top, double right, double bottom, double radiusX, double radiusY)需要传递6个参数,前4个参数和Rect.fromLTRB一样:

  • left代表左上角顶点的x坐标
  • top代表左上角顶点的y坐标
  • right代表右下角顶点的x坐标
  • bottom代表右下角顶点的y坐标
  • radiusX用来设置x方向的半径长度
  • radiusY用来设置y方向的半径长度
RRect rrect = const RRect.fromLTRBXY(50, 150, 320, 530, 90, 60);
canvas.drawRRect(rrect, paint);

image

RRect.fromLTRBAndCorners

从其左、上、右和下边缘以及 topLeft、topRight、bottomRight 和 bottomLeft 半径构造一个圆角矩形

RRect.fromLTRBAndCorners(
  double left, double top, double right, double bottom, {
    Radius topLeft = Radius.zero,
    Radius topRight = Radius.zero, 
    Radius bottomRight = Radius.zero,
    Radius bottomLeft = Radius.zero
})

可以传递8个参数,其中4个为必选的参数:

  • left代表左上角顶点的x坐标
  • top代表左上角顶点的y坐标
  • right代表右下角顶点的x坐标
  • bottom代表右下角顶点的y坐标
  • topLeft用来设置左上角的圆角半径
  • topRight用来设置右上角的圆角半径
  • bottomRight用来设置右下角角的圆角半径
  • bottomLeft用来设置左下角的圆角半径
RRect rrect = RRect.fromLTRBAndCorners(50, 120, 320, 420,
  topLeft: const Radius.circular(20),
  topRight: const Radius.circular(40),
  bottomLeft: const Radius.circular(60),
  bottomRight: const Radius.circular(80));
canvas.drawRRect(rrect, paint);

image

RRect.fromRectXY

根据方法名可以看出,需要依据一个矩形来绘制

RRect.fromRectXY( Rect rect, double radiusX, double radiusY)需要传递3个参数:

  • rect用来绘制一个矩形

  • radiusX用来设置x方向的半径长度

  • radiusY用来设置y方向的半径长度

Rect rect = Rect.fromPoints(const Offset(50, 180), const Offset(300, 420));
RRect rrect = RRect.fromRectXY(rect, 60.0, 100.0);
canvas.drawRRect(rrect, paint);

image

RRect.fromRectAndCorners

RRect.fromRectAndCorners( Rect rect,{ Radius topLeft = Radius.zero, Radius topRight = Radius.zero, Radius bottomRight = Radius.zero, Radius bottomLeft = Radius.zero})可以传递5个参数,1个必要的,4个可选的:

  • rect用来绘制一个矩形
  • bottom代表右下角顶点的y坐标
  • topLeft用来设置左上角的圆角半径
  • topRight用来设置右上角的圆角半径
  • bottomRight用来设置右下角角的圆角半径
  • bottomLeft用来设置左下角的圆角半径
Rect rect = Rect.fromPoints(const Offset(40, 125), const Offset(330, 520));
RRect rrect = RRect.fromRectAndCorners(rect,
  topLeft: const Radius.circular(60),
  topRight: const Radius.circular(40),
  bottomLeft: const Radius.circular(80),
  bottomRight: const Radius.circular(20));
canvas.drawRRect(rrect, paint);

image

drawArc 绘制圆弧

drawArc绘制一个以矩形为参照物的圆弧,需要传递5个属性:

  • Rect rect:矩形的位置和大小
  • double startAngle:圆弧开始的角度
  • double sweepAngle:圆弧开始到结束的角度大小
  • bool useCenter:是否向中心闭合
  • Paint paint:绘制对象
Rect rect = Rect.fromCenter(center: size.center(Offset.zero), width: 200, height: 200);
// 闭合
canvas.drawArc(rect, 0, 3.14, true, paint);
// 不闭合
canvas.drawArc(rect, 0, 3.14, false, paint);

image

drawColor 绘制颜色

drawColor绘制的颜色会沾满子整个屏幕。需要传递2个参数:

  • Color color:要绘制的颜色
  • BlendMode blendMode:颜色的混合模式
canvas.drawColor(Colors.blue, BlendMode.darken);

drawShadow绘制阴影

绘制阴影

drawShadow(Path path, Color color, double elevation, bool transparentOccluder)需要传递4个参数:

  • path是需要绘制阴影的路径
  • color是阴影的颜色
  • elevation是阴影的范围
  • transparentOccluder表示如果遮挡对象是透明的,应该为true,否则为false
Path path = Path();
path.moveTo(80, 200);
path.lineTo(320, 400);
path.lineTo(200, 340);
path.lineTo(100, 460);
path.close();
canvas.drawShadow(path, Colors.black, 8.0, false);
canvas.drawPath(path, paint);

image

drawVertices

绘制顶点。需要传递3个参数:

  • Vertices vertices:需要绘制的顶点对象
  • BlendMode blendMode:混合模式
  • Paint paint:绘制对象

该方法的重点在于Vertices 对象,它有5个参数:

  • VertexMode mode:顶点模式
  • List positions:设置所有顶点的坐标
  • List<Offset>? textureCoordinates:用于裁剪图像着色器中设置的图像。切割部分应用于三角形网格。请注意, textureCoordinates是图像上的坐标
  • List<Color>? colors:设置每个顶点处的颜色,数量必须和positions一样
  • List<int>? indices:如果提供了indices参数,则列表中的所有值都必须是positions的有效索引值

先来绘制一个最简单的:

Paint paint = Paint()..color = Colors.blue;
Vertices vertices = Vertices(
  VertexMode.triangles,
  const [ Offset.zero, Offset(-100, 120), Offset(60, 60)],
);
BlendMode blendMode = BlendMode.color;
canvas.drawVertices(vertices, blendMode, paint);

image

因为我的CustomPaint是包裹在Center组件中,所以它以中心为原点。如果没有Center组件,会以左上角为原点。

VertexMode

我们来研究一下VertexMode这个枚举类:

  • VertexMode.triangles:将三个点的每个序列绘制为三角形的顶点
  • VertexMode.triangleFan:绘制第一个点和两个点的每个滑动窗口作为三角形的顶点
  • VertexMode.triangleStrip:将三个点的每个滑动窗口绘制为三角形的顶点

这几句是官方文档写的,看起来很难理解,所以我引用【Flutter 专题】35 图解自定义 View 之 Canvas (三)这篇文章关于这几个值的描述,借用 A B C D E F G H I 来代替顶点:

  • VertexMode.triangles:每三个分割顶点相连,即 [A B C] [D E F] [G H I] 共 3
  • VertexMode.triangleStrip:每相邻的三个顶点相连,即 [A B C] [B C D] [C D E] [D E F] [E F G] [F G H] [G H I] 共 7
  • VertexMode.triangleFan:每相邻的两个顶点与首点相连,即 [A B C] [A C D] [A D E] [A E F] [A F G] [A G H] [A H I] 共 7

试用一下VertexMode.triangleStrip

Paint paint = Paint()..color = Colors.blue;
Vertices vertices = Vertices(
  VertexMode.triangleStrip,
  const [Offset(-200, -80), Offset.zero, Offset(-100, 120), Offset(60, 60)],
);
BlendMode blendMode = BlendMode.color;
canvas.drawVertices(vertices, blendMode, paint);

image

试用一下VertexMode.triangleFan

Paint paint = Paint()..color = Colors.blue;
Vertices vertices = Vertices(
  VertexMode.triangleFan,
  const [Offset(-200, -80), Offset.zero, Offset(-100, 120), Offset(60, 60), Offset(160, -120)],
);
BlendMode blendMode = BlendMode.color;
canvas.drawVertices(vertices, blendMode, paint);

image

colors

设置每个顶点处的颜色,数量必须和positions一样

Paint paint = Paint();
Vertices vertices = Vertices(
  VertexMode.triangleFan,
  const [Offset(-200, -80), Offset.zero, Offset(-100, 120), Offset(60, 60), Offset(160, -120)],
  colors: [Colors.red, Colors.orange, Colors.blue, Colors.yellow, Colors.green],
);
BlendMode blendMode = BlendMode.color;
canvas.drawVertices(vertices, blendMode, paint);

image

indices

用来过滤positions的索引

Paint paint = Paint()..color = Colors.blue;
Vertices vertices = Vertices(
  VertexMode.triangleFan,
  const [Offset(-200, -80), Offset.zero, Offset(-100, 120), Offset(60, 60), Offset(160, -120)],
  indices: [0, 1, 4],
);
BlendMode blendMode = BlendMode.color;
canvas.drawVertices(vertices, blendMode, paint);

image

textureCoordinates

裁剪图像着色器绘制的图片,数量必须和positions一样

canvas.drawImage(image, Offset.zero, Paint());
Paint paint = Paint()
  ..color = Colors.blue
  ..shader = ImageShader(image, TileMode.decal, TileMode.decal, Matrix4.skewX(0).storage);
Vertices vertices = Vertices(
  VertexMode.triangleFan,
  [
    Offset.zero,
    Offset(size.width, 0),
    Offset(0, size.height),
    Offset(size.width, size.height),
    Offset(size.width, 0),
  ],
  textureCoordinates: [
    Offset.zero,
    Offset(size.width - 100, 0),
    Offset(0, size.height - 100),
    Offset(size.width - 80, size.height - 80),
    Offset(size.width - 40, 0),
  ],
);
BlendMode blendMode = BlendMode.color;
canvas.drawVertices(vertices, blendMode, paint);

image

drawDRRect 绘制嵌套圆角矩形

drawDRRect绘制嵌套的两个矩形,outer圆角矩形的宽高必须大于等于inner圆角矩形的宽高。需要传递3个参数:

  • RRect outer:绘制外围圆角矩形
  • RRect inner:绘制内部圆角矩形
  • Paint paint:绘制对象
// 外围圆角矩形
Rect rectOuter = Rect.fromCenter(center: size.center(Offset.zero), width: 300, height: 300);
RRect outer = RRect.fromRectAndRadius(rectOuter, const Radius.circular(80));
// 内部圆角矩形
Rect rectInner = Rect.fromCenter(center: size.center(Offset.zero), width: 200, height: 100);
RRect inner = RRect.fromRectAndRadius(rectInner, const Radius.circular(60));
canvas.drawDRRect(outer, inner, paint);

image

drawLine 绘制线段

drawLine需要传递3个参数:

  • Offset p1:第一个点的位置
  • Offset p2:第二个点的位置
  • Paint paint:绘制对象
Offset p1 = const Offset(200, 50);
Offset p2 = const Offset(600, 200);
canvas.drawLine(p1, p2, paint);

image

drawOval 绘制椭圆

drawOval绘制一个轴对齐的椭圆。需要传递2个参数:

  • Rect rect:获取椭圆的原点和宽高
  • Paint paint:绘制对象
// 宽高不相等为椭圆
Rect rect = Rect.fromCenter(center: size.center(Offset.zero), width: 300, height: 200);
canvas.drawOval(rect, paint);
// 宽高相等为正圆
Rect rect = Rect.fromCenter(center: size.center(Offset.zero), width: 300, height: 300);
canvas.drawOval(rect, paint);

image

drawPoints 绘制点

drawPoints根据给定的PointMode绘制一系列点。需要传递3个参数:

  • PointMode pointMode:点的绘制模式
  • List points:点的坐标
  • Paint paint:绘制对象
List<Offset> points = const [
  Offset(100, 100), Offset(200, 200),
  Offset(100, 300), Offset(300, 400),
  Offset(200, 500), Offset(300, 400),
];
// 绘制多条线段,两个点一组
canvas.drawPoints(PointMode.lines, points, paint);
// 绘制点,由strokeCap控制点的样式
canvas.drawPoints(PointMode.points, points, paint);
// 将每一个点连接起来
canvas.drawPoints(PointMode.polygon, points, paint);

image

drawRawPoints 绘制点

绘制一系列点,需要传入3个参数:

  • PointMode pointMode:点的绘制模式
  • Float32List points:点的坐标
  • Paint paint:绘制对象
Paint paint = Paint()
  ..color = Colors.blue
  ..strokeWidth = 12;
Float32List points = Float32List.fromList([100, 120, 500, 250, 150, 300]);
canvas.drawRawPoints(PointMode.points, points, paint);

image

其他用法和drawPoints一样。

canvas.clipPath 裁剪路径

绘制一条路径,用来裁剪其他形状。有2个参数传递:

  • Path path:绘制裁剪的形状
  • bool doAntiAlias:是否抗锯齿
// 裁剪的形状、路径
Path path = Path();
path.lineTo(-100, -100);
path.lineTo(-100, 200);
path.close();
canvas.clipPath(path);
// 裁剪前的原图形
Paint paint = Paint()..color = Colors.blue;
canvas.drawCircle(size.center(Offset.zero), 120, paint);

image

canvas.clipRect 裁剪矩形

绘制一个矩形,用来裁剪其他形状。有3个参数传递:

  • Rect rect:绘制裁剪的形状
  • ClipOp clipOp:裁剪的类型
  • bool doAntiAlias:是否抗锯齿
// 裁剪的形状
Rect rect = Rect.fromCenter(center: size.center(Offset.zero), width: 200, height: 200);
canvas.clipRect(rect, clipOp: ClipOp.intersect);
// 裁剪前的原图形
Paint paint = Paint()..color = Colors.blue;
canvas.drawCircle(size.center(Offset.zero), 120, paint);

image

canvas.clipRRect 裁剪圆角矩形

绘制一个圆角矩形,用来裁剪其他形状。有2个参数传递:

  • RRect rrect:绘制裁剪的形状
  • bool doAntiAlias:是否抗锯齿
// 裁剪的形状
Rect rect = Rect.fromCenter(center: size.center(Offset.zero), width: 200, height: 200);
RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(80));
canvas.clipRRect(rrect);
// 裁剪前的原图形
Paint paint = Paint()..color = Colors.blue;
canvas.drawCircle(size.center(Offset.zero), 120, paint);

image

canvas.transform

对形状进行变形。只需要传递一个对象:

  • Float64List matrix4:将当前变换乘以指定的 4⨉4 变换矩阵,该矩阵指定为以列优先顺序排列的值列表
Paint paint = Paint()..color = Colors.blue;
Rect rect = Rect.fromCenter(center: size.center(Offset.zero), width: 200, height: 200);
canvas.transform(Matrix4.rotationZ(1).storage);
canvas.drawRect(rect, paint);

image

canvas.rotate

旋转图形。只有一个传递的属性:

  • double radians:旋转的角度,以3.14为标准
Paint paint = Paint()..color = Colors.blue;
Rect rect = Rect.fromCenter(center: size.center(Offset.zero), width: 200, height: 200);
canvas.rotate(1);
canvas.drawRect(rect, paint);

视图和canvas.transform的一样

canvas.scale

缩放图形。有2个参数可以传递:

  • double sx:水平方向缩放
  • double? sy:垂直方向缩放

只写一个参数,第二个参数默认一样

Paint paint = Paint()..color = Colors.blue;
Rect rect = Rect.fromCenter(center: size.center(Offset.zero), width: 200, height: 200);
canvas.scale(1.3);
canvas.drawRect(rect, paint);

image

canvas.skew

倾斜图形。需要传递2个参数:

  • double sx:围绕原点顺时针在运行单位上的水平倾斜
  • double sy:原点周围顺时针在运行单位上的垂直倾斜

两个参数的取值最好在 0-1 之间

Paint paint = Paint()..color = Colors.blue;
Rect rect = Rect.fromCenter(center: size.center(Offset.zero), width: 200, height: 200);
canvas.skew(.4, .2);
canvas.drawRect(rect, paint);

image

canvas.translate

偏移图形。需要传递2个参数:

  • double dx:水平移动距离
  • double dy:垂直移动距离
Paint paint = Paint()..color = Colors.blue;
Rect rect = Rect.fromCenter(center: size.center(Offset.zero), width: 200, height: 200);
canvas.translate(40, 40);
canvas.drawRect(rect, paint);

image

canvas.save和canvas.restore

canvas可以把之前的属性存储起来,canvas.restore在把存储的属性释放出来

Paint paint = Paint()..color = Colors.blue;
Rect rect = Rect.fromCenter(center: size.center(Offset.zero), width: 150, height: 150);
canvas.save();
canvas.scale(.8, .8);
canvas.translate(150, 150);
canvas.drawRect(rect, paint);
canvas.restore();
canvas.drawRect(rect, paint);

save方法是将它之前的属性拷贝了一份存起来。restore是将存起来的那部分代码释放出来,就是把save之前的内容复制一份,放在restore的位置。

image

canvas.getSaveCount

返回保存堆栈上的项目数,包括初始状态。这意味着它为干净的画布返回 1,并且每次调用save和saveLayer都会增加它,并且每次匹配的restore调用都会减少它。这个数字不能低于 1。

canvas.saveLayer

相当于clipRect + save 两个方法。传递2个参数:

  • Rect? bounds:绘制一个区域
  • Paint paint:画笔对象,主要是为了使用Paint.colorFilter和Paint.blendMode这两个属性

设置一个范围,图形只能在该范围显示,超出部分裁剪掉:

Paint paint = Paint()..color = Colors.blue;
Rect rect = Rect.fromCenter(center: size.center(Offset.zero), width: 150, height: 150);
canvas.drawRect(rect, paint);
Rect rect1 = const Rect.fromLTWH(100, 100, 200, 200);
canvas.drawRect(rect1, Paint()..color = Colors.red);
canvas.saveLayer(rect1, paint);
canvas.scale(.8, .8);
canvas.translate(150, 150);
canvas.drawRect(rect, paint);

image