CustomPaint
提供Canvas对象来进行绘制的组件
CustomPaint是Flutter中一个强大的绘图组件,允许开发者通过自定义画笔(CustomPainter)在画布(Canvas)上绘制任意2D图形。它不依赖预置的UI控件,而是直接使用底层绘
图API(如绘制路径、形状、文本或图像),适用于需要高度定制化视觉效果的场景,例如图表、动画、游戏界面或艺术化设计。
核心逻辑:
CustomPaint组件将绘图任务委托给一个CustomPainter对象。当组件需要渲染时,Flutter会调用CustomPainter的paint()方法,传入一个Canvas对象和可用区域大小,开发者在此方法内实现绘图指令。
使用场景
- 数据可视化: 绘制折线图、柱状图或圆形进度条。
- 自定义图标/按钮: 创建非标准形状的UI元素(如波浪形背景)。
- 游戏开发: 渲染2D游戏中的角色或场景。
- 艺术设计: 实现手绘风格或动态涂鸦效果。
示例
1. 绘制简单图形(圆形和矩形)
import 'package:flutter/material.dart';
class BasicPainter extends CustomPainter {
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
// 绘制一个圆形
canvas.drawCircle(Offset(size.width / 2, size.height / 2), 50, paint);
// 绘制一个矩形
paint.color = Colors.red;
canvas.drawRect(Rect.fromLTWH(0, 0, 100, 80), paint);
}
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
// 在 UI 中使用
class Example1 extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CustomPaint(
painter: BasicPainter(),
size: const Size(200, 200), // 指定绘图区域大小
),
),
);
}
}
2. 交互式绘图(动态更新颜色)
import 'package:flutter/material.dart';
class InteractivePainter extends CustomPainter {
final Color color;
InteractivePainter(this.color);
void paint(Canvas canvas, Size size) {
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
60,
Paint()..color = color,
);
}
bool shouldRepaint(covariant InteractivePainter oldDelegate) {
return oldDelegate.color != color; // 颜色变化时重绘
}
}
class Example2 extends StatefulWidget {
_Example2State createState() => _Example2State();
}
class _Example2State extends State<Example2> {
Color _currentColor = Colors.blue;
void _changeColor() {
setState(() {
_currentColor = _currentColor == Colors.blue ? Colors.green : Colors.blue;
});
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomPaint(
painter: InteractivePainter(_currentColor),
size: const Size(150, 150),
),
ElevatedButton(
onPressed: _changeColor,
child: const Text('切换颜色'),
),
],
),
),
);
}
}
3. 适配主题的复杂路径绘制
import 'package:flutter/material.dart';
class ThemeAwarePainter extends CustomPainter {
final Color primaryColor;
ThemeAwarePainter(this.primaryColor);
void paint(Canvas canvas, Size size) {
final path = Path()
..moveTo(0, size.height)
..quadraticBezierTo(
size.width / 2,
size.height - 80,
size.width,
size.height,
)
..lineTo(size.width, 0)
..lineTo(0, 0)
..close();
canvas.drawPath(
path,
Paint()
..color = primaryColor
..style = PaintingStyle.fill,
);
}
bool shouldRepaint(covariant ThemeAwarePainter oldDelegate) {
return oldDelegate.primaryColor != primaryColor;
}
}
class Example3 extends StatelessWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
body: CustomPaint(
painter: ThemeAwarePainter(theme.primaryColor),
size: MediaQuery.of(context).size, // 全屏绘制
),
);
}
}
注意点
常见问题与性能优化
- 性能瓶颈:
- 避免在
paint()方法中执行复杂计算或频繁分配内存(如创建新Paint对象)。应在初始化阶段预定义对象。 - 对于静态图形,设置
shouldRepaint返回false以防止不必要的重绘。
- 兼容性警告:
- 在不同设备分辨率下,确保使用
Size参数适配画布比例,避免图形拉伸或模糊。
- 优化技巧:
- 使用
RepaintBoundary包裹CustomPaint,将绘图隔离为独立图层,减少父组件重建的影响。 - 对于动态图形(如动画),考虑使用
CustomPaint的foregroundPainter属性分离静态和动态部分。
最佳实践
- 重用
Painter对象: 在StatefulWidget中缓存CustomPainter实例。 - 测试多平台: 在
iOS/Android/Web上验证绘图效果,尤其是路径和文本渲染。 - 使用
Canvas高级特性: 如save()/restore()管理绘图状态,或clipPath()实现裁剪效果。
构造函数
const CustomPaint({
Key? key,
this.painter, // 主绘图器(背景层)
this.foregroundPainter, // 前景绘图器(覆盖在子组件上方)
required this.size, // 画布大小(必须提供)
Widget? child, // 可选的子组件(绘制在背景和前景之间)
})
属性
| 属性名 | 属性类型 | 说明 |
|---|---|---|
painter | CustomPainter? | 主绘图器,绘制背景层内容。当需要更新时触发重绘。 |
foregroundPainter | CustomPainter? | 前景绘图器,绘制内容覆盖在 child 上方。适用于动态效果(如动画)。 |
size | Size | 画布的大小。如果为 Size.infinite,将使用父组件约束的最大范围。 |
child | Widget? | 可选的子组件,位于背景和前景绘图之间。 |
关键属性详解
size: 这是最关键的属性,直接影响绘图区域。如果未正确设置,可能导致图形溢出或空白。建议显式指定size以确保一致性。painter与foregroundPainter: 两者均为CustomPainter类型,但foregroundPainter更适合处理高频更新(如动画),因为它可以独立于主绘图器重绘,避免整体刷新。child: 用于在自定义图形上叠加交互元素(如按钮)。例如,在绘制一个自定义图表后,通过child添加一个可点击的标签。