Flutter CustomPainter 详解解锁自定义绘制的无限可能引言Flutter 提供了丰富的内置 Widget但有时我们需要实现一些复杂的、自定义的视觉效果。这时CustomPainter就成为了我们的最佳选择。它允许我们直接在 Canvas 上绘制图形、路径、文本等实现完全自定义的视觉效果。CustomPainter 基础概念什么是 CustomPainterCustomPainter是 Flutter 中用于自定义绘制的核心类它继承自Listenable可以监听依赖变化并触发重绘。CustomPainter 的核心方法class MyCustomPainter extends CustomPainter { override void paint(Canvas canvas, Size size) { // 绘制逻辑 } override bool shouldRepaint(covariant CustomPainter oldDelegate) { // 判断是否需要重绘 return true; } }paint方法接收Canvas和Size参数在 Canvas 上进行绘制操作。shouldRepaint方法判断是否需要重新绘制返回true表示需要重绘返回false表示不需要。Canvas 基础操作绘制基本图形override void paint(Canvas canvas, Size size) { // 设置画笔 final Paint paint Paint() ..color Colors.blue ..strokeWidth 2 ..style PaintingStyle.stroke; // 绘制圆形 canvas.drawCircle( Offset(size.width / 2, size.height / 2), 50, paint, ); // 绘制矩形 canvas.drawRect( Rect.fromLTWH(50, 50, 100, 80), paint, ); // 绘制椭圆 canvas.drawOval( Rect.fromLTWH(50, 150, 100, 60), paint, ); }绘制路径override void paint(Canvas canvas, Size size) { final Paint paint Paint() ..color Colors.red ..strokeWidth 3 ..style PaintingStyle.stroke; final Path path Path(); path.moveTo(50, 50); path.lineTo(150, 150); path.quadraticBezierTo(200, 200, 250, 100); path.cubicTo(300, 50, 350, 150, 400, 100); path.close(); canvas.drawPath(path, paint); }绘制文本override void paint(Canvas canvas, Size size) { final TextSpan textSpan TextSpan( text: Hello CustomPainter!, style: TextStyle( color: Colors.black, fontSize: 24, fontWeight: FontWeight.bold, ), ); final TextPainter textPainter TextPainter( text: textSpan, textDirection: TextDirection.ltr, ); textPainter.layout(minWidth: 0, maxWidth: size.width); textPainter.paint(canvas, Offset(50, 50)); }高级绘制技巧渐变效果override void paint(Canvas canvas, Size size) { // 线性渐变 final LinearGradient linearGradient LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Colors.blue, Colors.purple, Colors.pink], stops: [0.0, 0.5, 1.0], ); // 径向渐变 final RadialGradient radialGradient RadialGradient( center: Alignment.center, radius: 100, colors: [Colors.yellow, Colors.orange, Colors.red], ); canvas.drawRect( Offset.zero size, Paint()..shader radialGradient.createShader(Offset.zero size), ); }变换操作override void paint(Canvas canvas, Size size) { final Paint paint Paint() ..color Colors.green ..strokeWidth 2 ..style PaintingStyle.fill; // 保存当前状态 canvas.save(); // 平移 canvas.translate(size.width / 2, size.height / 2); // 旋转 canvas.rotate(pi / 4); // 缩放 canvas.scale(1.5); // 绘制矩形 canvas.drawRect( Rect.fromLTWH(-50, -50, 100, 100), paint, ); // 恢复之前的状态 canvas.restore(); }裁剪操作override void paint(Canvas canvas, Size size) { // 圆形裁剪 canvas.clipPath(Path() ..addOval(Rect.fromCircle( center: Offset(size.width / 2, size.height / 2), radius: 100, )) ); // 在裁剪区域内绘制 canvas.drawRect( Offset.zero size, Paint()..color Colors.blue, ); }实战案例自定义进度指示器class CircularProgressPainter extends CustomPainter { final double progress; final Color color; final double strokeWidth; CircularProgressPainter({ required this.progress, this.color Colors.blue, this.strokeWidth 8, }); override void paint(Canvas canvas, Size size) { final center Offset(size.width / 2, size.height / 2); final radius min(size.width, size.height) / 2 - strokeWidth / 2; // 绘制背景圆环 final backgroundPaint Paint() ..color Colors.grey[200]! ..strokeWidth strokeWidth ..style PaintingStyle.stroke ..strokeCap StrokeCap.round; canvas.drawCircle(center, radius, backgroundPaint); // 绘制进度圆环 final progressPaint Paint() ..color color ..strokeWidth strokeWidth ..style PaintingStyle.stroke ..strokeCap StrokeCap.round; final arcAngle 2 * pi * progress; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), -pi / 2, arcAngle, false, progressPaint, ); } override bool shouldRepaint(CircularProgressPainter oldDelegate) { return oldDelegate.progress ! progress || oldDelegate.color ! color || oldDelegate.strokeWidth ! strokeWidth; } }实战案例绘制波形图class WaveformPainter extends CustomPainter { final Listdouble data; final Color color; WaveformPainter({required this.data, this.color Colors.blue}); override void paint(Canvas canvas, Size size) { if (data.isEmpty) return; final paint Paint() ..color color ..strokeWidth 2 ..style PaintingStyle.stroke; final path Path(); final stepX size.width / (data.length - 1); final maxValue data.reduce(max); for (int i 0; i data.length; i) { final x i * stepX; final y size.height - (data[i] / maxValue) * size.height; if (i 0) { path.moveTo(x, y); } else { path.lineTo(x, y); } } canvas.drawPath(path, paint); } override bool shouldRepaint(WaveformPainter oldDelegate) { return oldDelegate.data ! data || oldDelegate.color ! color; } }性能优化建议1. 合理使用 shouldRepaintoverride bool shouldRepaint(MyPainter oldDelegate) { return oldDelegate.value ! value; }只有当依赖的数据变化时才返回true避免不必要的重绘。2. 使用 repaintBoundaryRepaintBoundary( child: CustomPaint(painter: MyPainter()), )将 CustomPaint 包裹在RepaintBoundary中可以隔离绘制区域避免影响其他 Widget。3. 缓存复杂路径class MyPainter extends CustomPainter { Path? _cachedPath; override void paint(Canvas canvas, Size size) { if (_cachedPath null) { _cachedPath _createComplexPath(); } canvas.drawPath(_cachedPath!, Paint()); } }对于复杂路径将其缓存起来避免每次重绘都重新计算。4. 使用 PictureRecorderfinal recorder PictureRecorder(); final canvas Canvas(recorder); // 在 canvas 上绘制 canvas.drawCircle(Offset(50, 50), 30, Paint()..color Colors.red); final picture recorder.endRecording(); // 绘制 picture canvas.drawPicture(picture);对于不需要频繁变化的复杂图形可以预先录制为 Picture。结合 Animation 实现动态效果class AnimatedPainter extends StatefulWidget { override _AnimatedPainterState createState() _AnimatedPainterState(); } class _AnimatedPainterState extends StateAnimatedPainter with SingleTickerProviderStateMixin { late AnimationController _controller; late Animationdouble _animation; override void initState() { super.initState(); _controller AnimationController( duration: Duration(seconds: 2), vsync: this, )..repeat(); _animation Tweendouble(begin: 0, end: 1).animate(_controller); } override Widget build(BuildContext context) { return AnimatedBuilder( animation: _animation, builder: (context, child) { return CustomPaint( painter: ProgressPainter(progress: _animation.value), ); }, ); } }总结CustomPainter是 Flutter 中实现自定义绘制的强大工具通过掌握 Canvas 的各种绘制方法和技巧我们可以实现各种复杂的视觉效果。在实际应用中需要注意性能优化合理使用缓存和重绘策略以确保流畅的用户体验。掌握 CustomPainter 后你可以创建自定义的图表和数据可视化组件实现复杂的动画效果绘制独特的 UI 元素和装饰构建游戏中的图形渲染让你的创意在 Canvas 上自由驰骋吧