cover

Flutter 绘制三角网格 Triangular Mesh

前言

本文实现 Generative Artistry 教程的第 4 篇图形 Triangular mesh 效果如下。

没按顺序来,因为第二篇 Joy Division 我还没实现 😝

创建画布

首先创建一个用于绘制的画布,然后实现 TriangularMeshPainterpaint 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class TriangularMesh extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: TriangularMeshPainter(),
);
}
}

class TriangularMeshPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {}
@override
bool shouldRepaint(TriangularMeshPainter oldDelegate) => false;
}

绘制端点

声明一个 gap 变量等比分割画布,在纵轴,横轴方向每隔 gap 长度使用 canvas.drawCircle 方法绘制圆点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class TriangularMeshPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// 声明 line 和 lines 变量存储点和线
List line;
List lines = [];
double gap = size.width / 8;

// 设置点的绘制属性
Paint paint = Paint()
..color = Colors.black
..isAntiAlias = true;

// 纵轴方向
for (double y = gap / 2; y <= size.height; y += gap) {
line = [];
// 横轴方向
for (double x = gap / 4; x <= size.width; x += gap) {
line.add({"x": x, "y": y});
// 绘制圆,参数分别是圆点位置,圆的半径,绘制属性
canvas.drawCircle(Offset(x, y), 1, paint);
}
lines.add(line);
}
}
}

创建一个 Point 类,表示一个绘制的圆点。

1
2
3
4
class Point {
double x;
double y;
}

为了使点之间形成三角形,使用一个 odd 变量,在每次添加圆点的时候改变点的横轴值,形成交错效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@override
void paint(Canvas canvas, Size size) {
bool odd = false;
List<Point> line;
List<List<Point>> lines = [];
double gap = size.width / 8;

Paint paint = Paint()
..color = Colors.black
..isAntiAlias = true;

for (double y = gap / 2; y <= size.height; y += gap) {
// 每次添加圆点之前对 odd 取反
odd = !odd;
line = [];

for (double x = gap / 4; x <= size.width; x += gap) {
Point point = Point();
// 赋值圆点横轴位置时根据 odd 变量判断是否需要增加距离
point.x = x + (odd ? gap / 2 : 0);
point.y = y;
// 将点添加到 line 数组
line.add(point);
canvas.drawCircle(Offset(point.x, point.y), 1, paint);
}

lines.add(line);
}
}

绘制网格

接下来需要建立 3 个点之间的关系,从而绘制三角形。首先创建一个接收三角形三个坐标,并连接绘制它们的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void _drawTriangle(Canvas canvas, Point pointA, Point pointB, Point pointC) {
// 使用一个路径链接 3 点并绘制这条路径
Path path = Path();
Paint line = Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeJoin = StrokeJoin.bevel
..isAntiAlias = true;

path.moveTo(pointA.x, pointA.y);
path.lineTo(pointB.x, pointB.y);
path.lineTo(pointC.x, pointC.y);
path.lineTo(pointA.x, pointA.y);
// 参数分别是路径,路径的绘制属性
canvas.drawPath(path, line);
}

然后遍历所有存储的线,并组合相邻线的点以形成三角形。

这部分也许会有点难以理解。脚本会遍历所有线,并组合相邻线的点以形成三角形。为了便于理解,我们将相邻的两条线分别称为 a 和 b。然后将两线符合要求的点合并到一个数组中,使其看起来像“之”字型:a1、b1、a2、b2、a3 以此类推。
这将为我们提供了一个含有三角形所有坐标的数组。如:[a1, b1, a2]、[b1, a2, b2], [a2, b2, a3] 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@override
void paint(Canvas canvas, Size size) {
///...

List dotLine;
odd = true;

for (int y = 0; y < lines.length - 1; y++) {
odd = !odd;
dotLine = [];
for (var i = 0; i < lines[y].length; i++) {
dotLine.add(odd ? lines[y][i] : lines[y + 1][i]);
dotLine.add(odd ? lines[y + 1][i] : lines[y][i]);
}

for (int i = 0; i < dotLine.length - 2; i++) {
_drawTriangle(canvas, dotLine[i], dotLine[i + 1], dotLine[i + 2]);
}
}
}

创建圆点时加入随机量,形成不规则的三角形。

1
2
3
4
5
6
7
for (double x = gap / 4; x <= size.width; x += gap) {
Point point = Point();
double random = (Random().nextDouble() * .8 - .4) * gap;
point.x = x + random + (odd ? gap / 2 : 0);
point.y = y + (Random().nextDouble() * .8 - .4) * gap;
line.add(point);
}

添加颜色

最后在绘制三角形函数那里加上一些颜色,只需要设置路径的 colorstyle 绘制属性即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void _drawTriangle(Canvas canvas, Point pointA, Point pointB, Point pointC) {
Path path = Path();
Paint fill = Paint()
// 添加颜色,可以尝试设置不同的颜色
..color = Colors.black.withOpacity(Random().nextDouble() * .9)
// ..color = colors[Random().nextInt(colors.length)].withOpacity(.8)
// 将路径的绘制样式设置为 fill
..style = PaintingStyle.fill
..strokeJoin = StrokeJoin.bevel
..isAntiAlias = true;

Paint line = Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeJoin = StrokeJoin.bevel
..isAntiAlias = true;

path.moveTo(pointA.x, pointA.y);
path.lineTo(pointB.x, pointB.y);
path.lineTo(pointC.x, pointC.y);
path.lineTo(pointA.x, pointA.y);

canvas.drawPath(path, fill);
// 新增一个路径绘制
canvas.drawPath(path, line);
}

大功告成!👏

代码地址

参考

Triangular Mesh

Buy Me A Coffee
← 使用 Github Action 发布 Flutter 插件 Flutter 绘制瓷砖线 Tiled Lines →