Flutter 自定义绘制完全指南
Flutter 自定义绘制完全指南
引言
自定义绘制是 Flutter 中创建独特视觉效果的强大工具,它允许开发者直接在 Canvas 上绘制图形。本文将深入探讨 CustomPainter 的各种用法和高级技巧。
基础概念回顾
CustomPainter
class MyPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { // 绘制逻辑 } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } } // 使用 CustomPaint( painter: MyPainter(), child: Container(), )Paint 对象
final paint = Paint() ..color = Colors.blue ..strokeWidth = 2 ..style = PaintingStyle.fill;高级技巧一:基本图形绘制
绘制圆形
void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = min(size.width, size.height) / 2; final paint = Paint() ..color = Colors.blue ..style = PaintingStyle.fill; canvas.drawCircle(center, radius, paint); }绘制矩形
void paint(Canvas canvas, Size size) { final rect = Rect.fromLTWH(10, 10, size.width - 20, size.height - 20); final paint = Paint() ..color = Colors.red ..style = PaintingStyle.stroke ..strokeWidth = 2; canvas.drawRect(rect, paint); }绘制路径
void paint(Canvas canvas, Size size) { final path = Path() ..moveTo(0, size.height / 2) ..lineTo(size.width / 2, 0) ..lineTo(size.width, size.height / 2) ..lineTo(size.width / 2, size.height) ..close(); final paint = Paint() ..color = Colors.green ..style = PaintingStyle.fill; canvas.drawPath(path, paint); }高级技巧二:渐变绘制
线性渐变
void paint(Canvas canvas, Size size) { final rect = Rect.fromLTWH(0, 0, size.width, size.height); final gradient = LinearGradient( colors: [Colors.blue, Colors.purple], begin: Alignment.topLeft, end: Alignment.bottomRight, ); final paint = Paint()..shader = gradient.createShader(rect); canvas.drawRect(rect, paint); }径向渐变
void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = min(size.width, size.height) / 2; final gradient = RadialGradient( colors: [Colors.yellow, Colors.orange, Colors.red], center: center, radius: radius, ); final paint = Paint()..shader = gradient.createShader( Rect.fromCircle(center: center, radius: radius) ); canvas.drawCircle(center, radius, paint); }扫描渐变
void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final gradient = SweepGradient( colors: [Colors.red, Colors.yellow, Colors.green, Colors.blue, Colors.red], center: center, startAngle: 0, endAngle: 2 * pi, ); final paint = Paint()..shader = gradient.createShader( Rect.fromCircle(center: center, radius: min(size.width, size.height) / 2) ); canvas.drawCircle(center, min(size.width, size.height) / 2, paint); }高级技巧三:路径动画
绘制动画路径
class AnimatedPathPainter extends CustomPainter { final Animation<double> animation; AnimatedPathPainter(this.animation) : super(repaint: animation); @override void paint(Canvas canvas, Size size) { final path = Path() ..moveTo(0, size.height / 2) ..quadraticBezierTo( size.width / 4, 0, size.width / 2, size.height / 2, ) ..quadraticBezierTo( size.width * 3 / 4, size.height, size.width, size.height / 2, ); final metrics = path.computeMetrics().first; final length = metrics.length * animation.value; final extractPath = metrics.extractPath(0, length); final paint = Paint() ..color = Colors.blue ..strokeWidth = 4 ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round; canvas.drawPath(extractPath, paint); } @override bool shouldRepaint(covariant AnimatedPathPainter oldDelegate) { return animation != oldDelegate.animation; } }高级技巧四:图像合成
绘制图像
void paint(Canvas canvas, Size size) async { final image = await loadImage('image.png'); canvas.drawImage(image, Offset.zero, Paint()); }图像裁剪
void paint(Canvas canvas, Size size) { final rect = Rect.fromLTWH(0, 0, size.width, size.height); final clipPath = Path()..addOval(rect); canvas.clipPath(clipPath); // 绘制内容 canvas.drawRect(rect, Paint()..color = Colors.blue); }实战案例:绘制仪表盘
class GaugePainter extends CustomPainter { final double value; GaugePainter({required this.value}); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = min(size.width, size.height) / 2 - 20; // 绘制背景弧 final backgroundPaint = Paint() ..color = Colors.grey[200]! ..strokeWidth = 12 ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round; final backgroundArc = Path() ..arcTo( Rect.fromCircle(center: center, radius: radius), pi * 1.2, pi * 0.6, false, ); canvas.drawPath(backgroundArc, backgroundPaint); // 绘制进度弧 final progressPaint = Paint() ..color = Colors.blue ..strokeWidth = 12 ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round; final progressArc = Path() ..arcTo( Rect.fromCircle(center: center, radius: radius), pi * 1.2, pi * 0.6 * value, false, ); canvas.drawPath(progressArc, progressPaint); // 绘制中心文字 final textPainter = TextPainter( text: TextSpan( text: '${(value * 100).toInt()}%', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), textAlign: TextAlign.center, textDirection: TextDirection.ltr, ); textPainter.layout(); textPainter.paint( canvas, Offset( center.dx - textPainter.width / 2, center.dy - textPainter.height / 2, ), ); } @override bool shouldRepaint(covariant GaugePainter oldDelegate) { return value != oldDelegate.value; } }实战案例:绘制波浪动画
class WavePainter extends CustomPainter { final Animation<double> animation; WavePainter(this.animation) : super(repaint: animation); @override void paint(Canvas canvas, Size size) { final wavePath = Path(); for (double x = 0; x <= size.width; x += 5) { double y = size.height / 2 + sin(x / 50 + animation.value * 2 * pi) * 30 + sin(x / 30 + animation.value * 2 * pi) * 20; if (x == 0) { wavePath.moveTo(x, y); } else { wavePath.lineTo(x, y); } } wavePath.lineTo(size.width, size.height); wavePath.lineTo(0, size.height); wavePath.close(); final gradient = LinearGradient( colors: [Colors.blue.withOpacity(0.5), Colors.blue], begin: Alignment.topCenter, end: Alignment.bottomCenter, ); final paint = Paint()..shader = gradient.createShader( Rect.fromLTWH(0, 0, size.width, size.height) ); canvas.drawPath(wavePath, paint); } @override bool shouldRepaint(covariant WavePainter oldDelegate) { return animation != oldDelegate.animation; } }实战案例:绘制饼图
class PieChartPainter extends CustomPainter { final List<double> values; final List<Color> colors; PieChartPainter({required this.values, required this.colors}); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = min(size.width, size.height) / 2 - 20; double startAngle = -pi / 2; double total = values.reduce((a, b) => a + b); for (int i = 0; i < values.length; i++) { final sweepAngle = (values[i] / total) * 2 * pi; final paint = Paint()..color = colors[i]; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), startAngle, sweepAngle, true, paint, ); startAngle += sweepAngle; } } @override bool shouldRepaint(covariant PieChartPainter oldDelegate) { return values != oldDelegate.values || colors != oldDelegate.colors; } }常见问题与解决方案
Q1:如何提高绘制性能?
A:优化绘制代码:
// 推荐 @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; // 仅在必要时重绘 } // 缓存 Paint 对象 final Paint _paint = Paint()..color = Colors.blue;Q2:如何绘制文字?
A:使用 TextPainter:
final textPainter = TextPainter( text: TextSpan(text: 'Hello'), textDirection: TextDirection.ltr, ); textPainter.layout(); textPainter.paint(canvas, Offset(10, 10));Q3:如何绘制图像?
A:使用 drawImage:
final image = await rootBundle.load('assets/image.png'); final decodedImage = await decodeImageFromList(image.buffer.asUint8List()); canvas.drawImage(decodedImage, Offset.zero, Paint());最佳实践
1. 缓存 Paint 对象
// 推荐 class MyPainter extends CustomPainter { final Paint _paint = Paint()..color = Colors.blue; @override void paint(Canvas canvas, Size size) { canvas.drawCircle(Offset(50, 50), 20, _paint); } } // 不推荐 class MyPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint()..color = Colors.blue; // 每次都创建 canvas.drawCircle(Offset(50, 50), 20, paint); } }2. 合理重绘
// 推荐 @override bool shouldRepaint(covariant MyPainter oldDelegate) { return value != oldDelegate.value; } // 不推荐 @override bool shouldRepaint(covariant MyPainter oldDelegate) { return true; // 每次都重绘 }总结
Flutter 的自定义绘制功能非常强大和灵活。通过本文的学习,你应该能够:
- 使用 CustomPainter 绘制基本图形
- 创建渐变效果
- 实现路径动画
- 绘制图表和可视化
- 优化绘制性能
掌握这些技巧,能够帮助你创建更加独特和吸引人的视觉效果。
