3. 绘制基础图形:点、线、矩形、圆角矩形、圆形、椭圆
好,咱们进入真正的绘制环节了。前面讲了画布和画笔,现在该让它们干活了。
基础图形是自定义绘制的基石。说白了,你以后画的任何复杂界面,拆开来看,无非就是这些基本元素的组合。我个人习惯先把这些基础图形练熟,后面画图表、画动画时才不会手忙脚乱。
3.1 点:最小的绘制单元
在Flutter的Canvas里,点其实不是「画一个像素点」,而是画一个很小的圆。为什么?因为Canvas没有专门的「画点」方法,我们通常用drawCircle来实现。
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.black
..strokeWidth = 2.0;
// 画一个点:在 (100, 100) 位置,半径为 3
canvas.drawCircle(Offset(100, 100), 3.0, paint);
}
嗯,这里要注意:半径设得越小,点就越细。但别设成0,那样就看不见了。我一般设2到4之间,看起来像个正常的点。
小技巧:如果你需要画很多点(比如散点图),别用循环一个个画,性能会很差。改用
canvas.drawPoints(),一次调用搞定。
3.2 线:直线与路径
画线用drawLine,需要两个点:起点和终点。
canvas.drawLine(
Offset(50, 50), // 起点
Offset(200, 50), // 终点
paint,
);
我曾经在项目里画折线图时,一开始用drawLine一条条画,代码又长又乱。后来改用Path,清爽多了:
final path = Path();
path.moveTo(50, 100); // 起点
path.lineTo(100, 150); // 画到第二个点
path.lineTo(150, 80); // 画到第三个点
path.lineTo(200, 120); // 画到第四个点
canvas.drawPath(path, paint);
你想想看,如果数据点有几十个,用drawLine得写多少行?用Path一个循环就搞定了。
注意:画线时,
paint.style要设为PaintingStyle.stroke,否则画出来的是实心形状,不是线。我刚开始学时就踩过这个坑。
3.3 矩形:最常用的形状
矩形有两种画法:drawRect和drawRRect。前者画直角矩形,后者画圆角矩形。
// 直角矩形
final rect = Rect.fromLTWH(50, 50, 150, 100);
canvas.drawRect(rect, paint);
// 圆角矩形
final rrect = RRect.fromRectAndRadius(
Rect.fromLTWH(50, 200, 150, 100),
Radius.circular(12),
);
canvas.drawRRect(rrect, paint);
这里有个细节:Rect.fromLTWH的参数分别是 left、top、width、height。别搞混了顺序,我见过有人把width和height写反,结果矩形变形了。
| 方法 | 说明 | 适用场景 |
|---|---|---|
| fromLTWH | 左上角坐标 + 宽高 | 最常用 |
| fromCircle | 圆心 + 半径 | 画正方形外接矩形 |
| fromPoints | 两个对角点 | 动态计算矩形 |
3.4 圆形与椭圆
圆形用drawCircle,椭圆用drawOval。其实椭圆就是「被拉伸的圆」。
// 圆形:圆心 (100, 100),半径 50
canvas.drawCircle(Offset(100, 100), 50, paint);
// 椭圆:在矩形区域内绘制
final ovalRect = Rect.fromLTWH(50, 200, 150, 100);
canvas.drawOval(ovalRect, paint);
为什么椭圆要用矩形来定义?因为椭圆本质上就是矩形的内切图形。你给一个正方形,它就画正圆;给一个长方形,它就画椭圆。
核心理解:在Flutter Canvas中,所有「不规则」的图形,都可以用矩形来约束。圆角矩形、椭圆、甚至弧线,都是基于矩形区域计算的。
3.5 实战:画一个简单的仪表盘
光说不练假把式。咱们用刚学的知识,画一个简单的仪表盘背景:
void paint(Canvas canvas, Size size) {
final bgPaint = Paint()
..color = Colors.grey[200]!
..style = PaintingStyle.fill;
final borderPaint = Paint()
..color = Colors.blue
..style = PaintingStyle.stroke
..strokeWidth = 3.0;
// 画背景圆
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
100,
bgPaint,
);
// 画边框圆
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
100,
borderPaint,
);
// 画刻度线(用矩形模拟)
for (int i = 0; i < 12; i++) {
double angle = i * 30 * 3.14159 / 180;
double x1 = size.width / 2 + 80 * cos(angle);
double y1 = size.height / 2 + 80 * sin(angle);
double x2 = size.width / 2 + 95 * cos(angle);
double y2 = size.height / 2 + 95 * sin(angle);
canvas.drawLine(
Offset(x1, y1),
Offset(x2, y2),
borderPaint,
);
}
}
这段代码里,圆形是仪表盘的外壳,线条是刻度。你看,基础图形组合起来,就能做出像模像样的东西了。
我的经验:刚开始练习时,别急着画复杂图形。先把点、线、矩形、圆这四种图形练熟,每个都画上几十遍。等你闭着眼睛都能写出
drawCircle的参数顺序时,再去看那些炫酷的自定义绘制,会发现——哦,原来都是这些基础图形的排列组合。
好了,基础图形就讲到这里。下一节咱们聊聊路径的高级玩法,比如贝塞尔曲线和弧线。那些才是真正让绘制「活起来」的东西。