Flutter CustomPainter 高级绘制详解
一、CustomPainter 概述
CustomPainter 是 Flutter 中用于自定义绘制的核心组件,可以实现各种复杂的图形效果。通过 Canvas API,可以绘制线条、形状、渐变、阴影等。
二、基础绘制
2.1 创建 CustomPainter
class MyPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { // 绘制代码 } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }2.2 使用 CustomPaint
CustomPaint( painter: MyPainter(), child: const Text('Custom Painter'), )三、绘制基础图形
3.1 绘制线条
void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.blue ..strokeWidth = 2 ..strokeCap = StrokeCap.round; canvas.drawLine( const Offset(10, 10), Offset(size.width - 10, size.height - 10), paint, ); }3.2 绘制矩形
void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.blue ..style = PaintingStyle.fill; canvas.drawRect( Rect.fromLTWH(10, 10, 100, 50), paint, ); // 圆角矩形 canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromLTWH(10, 70, 100, 50), const Radius.circular(8), ), paint..color = Colors.green, ); }3.3 绘制圆形和椭圆
void paint(Canvas canvas, Size size) { final paint = Paint()..color = Colors.blue; // 圆形 canvas.drawCircle( Offset(size.width / 2, size.height / 2), 50, paint, ); // 椭圆 canvas.drawOval( Rect.fromLTWH(10, 10, 100, 60), paint..color = Colors.green, ); }3.4 绘制路径
void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.blue ..strokeWidth = 2 ..style = PaintingStyle.stroke; final path = Path() ..moveTo(10, 10) ..lineTo(100, 10) ..lineTo(100, 80) ..close(); canvas.drawPath(path, paint); }四、渐变绘制
4.1 线性渐变
void paint(Canvas canvas, Size size) { final gradient = LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Colors.blue, Colors.purple], stops: const [0.0, 1.0], ); final paint = Paint()..shader = gradient.createShader( Rect.fromLTWH(0, 0, size.width, size.height), ); canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint); }4.2 径向渐变
void paint(Canvas canvas, Size size) { final gradient = RadialGradient( center: const Alignment(0.5, 0.5), radius: 0.5, colors: [Colors.blue, Colors.transparent], ); final paint = Paint()..shader = gradient.createShader( Rect.fromLTWH(0, 0, size.width, size.height), ); canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint); }4.3 扫描渐变
void paint(Canvas canvas, Size size) { final gradient = SweepGradient( center: const Alignment(0.5, 0.5), startAngle: 0, endAngle: 2 * pi, colors: [Colors.red, Colors.green, Colors.blue, Colors.red], ); final paint = Paint()..shader = gradient.createShader( Rect.fromCircle( center: Offset(size.width / 2, size.height / 2), radius: 100, ), ); canvas.drawCircle( Offset(size.width / 2, size.height / 2), 100, paint, ); }五、阴影和效果
5.1 阴影
void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.blue ..shadowColor = Colors.black ..shadowBlur = 10 ..shadowOffset = const Offset(2, 2); canvas.drawRect(Rect.fromLTWH(50, 50, 100, 100), paint); }5.2 模糊滤镜
void paint(Canvas canvas, Size size) { final rect = Rect.fromLTWH(50, 50, 100, 100); // 保存画布状态 canvas.save(); // 应用模糊滤镜 canvas.drawRect(rect, Paint()..color = Colors.blue); // 恢复画布状态 canvas.restore(); }六、变换
6.1 平移
void paint(Canvas canvas, Size size) { final paint = Paint()..color = Colors.blue; canvas.save(); canvas.translate(50, 50); canvas.drawRect(const Rect.fromLTWH(0, 0, 100, 100), paint); canvas.restore(); }6.2 旋转
void paint(Canvas canvas, Size size) { final paint = Paint()..color = Colors.blue; canvas.save(); canvas.translate(size.width / 2, size.height / 2); canvas.rotate(pi / 4); canvas.drawRect(const Rect.fromLTWH(-50, -50, 100, 100), paint); canvas.restore(); }6.3 缩放
void paint(Canvas canvas, Size size) { final paint = Paint()..color = Colors.blue; canvas.save(); canvas.scale(2); canvas.drawRect(const Rect.fromLTWH(25, 25, 50, 50), paint); canvas.restore(); }七、高级绘制技巧
7.1 绘制虚线
void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.blue ..strokeWidth = 2 ..style = PaintingStyle.stroke; final path = Path(); const dashWidth = 10; const dashSpace = 5; double startX = 0; while (startX < size.width) { path.moveTo(startX, 50); path.lineTo(startX + dashWidth, 50); startX += dashWidth + dashSpace; } canvas.drawPath(path, paint); }7.2 绘制圆角路径
void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.blue ..style = PaintingStyle.fill; final path = Path() ..moveTo(50, 50) ..lineTo(150, 50) ..quadraticBezierTo(170, 50, 170, 70) ..lineTo(170, 150) ..quadraticBezierTo(170, 170, 150, 170) ..lineTo(50, 170) ..quadraticBezierTo(30, 170, 30, 150) ..lineTo(30, 70) ..quadraticBezierTo(30, 50, 50, 50) ..close(); canvas.drawPath(path, paint); }7.3 绘制文字
void paint(Canvas canvas, Size size) { final textStyle = TextStyle( color: Colors.black, fontSize: 24, fontWeight: FontWeight.bold, ); final textSpan = TextSpan( text: 'Hello, CustomPainter!', style: textStyle, ); final textPainter = TextPainter( text: textSpan, textDirection: TextDirection.ltr, ); textPainter.layout( minWidth: 0, maxWidth: size.width, ); textPainter.paint( canvas, Offset( (size.width - textPainter.width) / 2, (size.height - textPainter.height) / 2, ), ); }八、实战案例:仪表盘
class GaugePainter extends CustomPainter { final double value; final double min; final double max; GaugePainter({ required this.value, this.min = 0, this.max = 100, }); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = size.width / 2 - 20; // 绘制背景弧 final backgroundPaint = Paint() ..color = Colors.grey[200]! ..strokeWidth = 12 ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), pi, pi, false, backgroundPaint, ); // 绘制进度弧 final progress = (value - min) / (max - min); final progressPaint = Paint() ..color = Colors.blue ..strokeWidth = 12 ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), pi, pi * progress, false, progressPaint, ); // 绘制指针 final pointerPaint = Paint() ..color = Colors.blue ..strokeWidth = 3 ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round; final angle = pi + (pi * progress); final pointerLength = radius - 30; final endPoint = Offset( center.dx + cos(angle) * pointerLength, center.dy + sin(angle) * pointerLength, ); canvas.drawLine(center, endPoint, pointerPaint); // 绘制中心圆 final centerPaint = Paint()..color = Colors.blue; canvas.drawCircle(center, 8, centerPaint); // 绘制刻度 for (int i = 0; i <= 10; i++) { final angle = pi + (pi * i / 10); final startRadius = radius - 20; final endRadius = radius - (i % 5 == 0 ? 30 : 25); final startPoint = Offset( center.dx + cos(angle) * startRadius, center.dy + sin(angle) * startRadius, ); final endPoint = Offset( center.dx + cos(angle) * endRadius, center.dy + sin(angle) * endRadius, ); canvas.drawLine( startPoint, endPoint, Paint() ..color = Colors.grey[600]! ..strokeWidth = i % 5 == 0 ? 2 : 1, ); } // 绘制数值 final textPainter = TextPainter( text: TextSpan( text: '${value.round()}%', style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black, ), ), textDirection: TextDirection.ltr, ); textPainter.layout(); textPainter.paint( canvas, Offset( center.dx - textPainter.width / 2, center.dy + 30, ), ); } @override bool shouldRepaint(covariant GaugePainter oldDelegate) { return value != oldDelegate.value; } }九、动画绘制
class AnimatedPainter extends CustomPainter { final Animation<double> animation; AnimatedPainter({required this.animation}) : super(repaint: animation); @override void paint(Canvas canvas, Size size) { final progress = animation.value; final center = Offset(size.width / 2, size.height / 2); final radius = progress * 100; final paint = Paint() ..color = Colors.blue ..style = PaintingStyle.fill; canvas.drawCircle(center, radius, paint); } @override bool shouldRepaint(covariant AnimatedPainter oldDelegate) { return animation != oldDelegate.animation; } } // 使用 class AnimatedCircle extends StatefulWidget { const AnimatedCircle({super.key}); @override State<AnimatedCircle> createState() => _AnimatedCircleState(); } class _AnimatedCircleState extends State<AnimatedCircle> with SingleTickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(seconds: 2), )..repeat(reverse: true); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return CustomPaint( painter: AnimatedPainter(animation: _controller), size: const Size(200, 200), ); } }十、总结
CustomPainter 是 Flutter 中强大的自定义绘制工具:
- 基础图形- 线条、矩形、圆形、路径
- 渐变- 线性、径向、扫描渐变
- 效果- 阴影、模糊
- 变换- 平移、旋转、缩放
- 高级技巧- 虚线、圆角路径、文字绘制
- 动画- 结合 Animation 实现动态绘制
掌握 CustomPainter 可以实现各种复杂的视觉效果。