Flutter 自定义绘制:用代码作画的艺术

张开发
2026/4/10 8:36:55 15 分钟阅读

分享文章

Flutter 自定义绘制:用代码作画的艺术
Flutter 自定义绘制用代码作画的艺术掌握 Flutter 自定义绘制的高级技巧在画布上创造令人惊艳的视觉效果。一、自定义绘制概述作为一名把代码当散文写的 UI 匠人我对 Flutter 自定义绘制有着独特的见解。CustomPainter 是 Flutter 中最强大的绘制工具它让我们可以直接在画布上作画创造出任何我们想要的视觉效果。从简单的几何图形到复杂的艺术作品CustomPainter 为我们提供了无限的创作可能。就像画家手里的画笔代码在我们手中也能创造出令人惊叹的画面。二、基础绘制1. CustomPainter 基础import package:flutter/material.dart; // 基础自定义绘制 class BasicPainter extends CustomPainter { override void paint(Canvas canvas, Size size) { // 创建画笔 final paint Paint() ..color Colors.blue ..style PaintingStyle.fill; // 绘制矩形 canvas.drawRect( Rect.fromLTWH(50, 50, 200, 150), paint, ); // 绘制圆形 paint.color Colors.red; canvas.drawCircle( Offset(size.width / 2, size.height / 2), 100, paint, ); // 绘制线条 paint.color Colors.green; paint.style PaintingStyle.stroke; paint.strokeWidth 5; canvas.drawLine( Offset(0, size.height), Offset(size.width, 0), paint, ); } override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } } // 使用自定义绘制 class BasicPainterWidget extends StatelessWidget { override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(基础绘制)), body: Center( child: CustomPaint( painter: BasicPainter(), size: Size(400, 400), ), ), ); } }2. 路径绘制// 路径绘制 class PathPainter extends CustomPainter { override void paint(Canvas canvas, Size size) { final paint Paint() ..color Colors.purple ..style PaintingStyle.stroke ..strokeWidth 3; // 创建路径 final path Path(); // 移动到起点 path.moveTo(50, 50); // 绘制直线 path.lineTo(200, 50); // 绘制贝塞尔曲线 path.quadraticBezierTo(300, 100, 250, 200); // 绘制弧线 path.arcToPoint( Offset(150, 250), radius: Radius.circular(50), clockwise: false, ); // 闭合路径 path.close(); // 绘制路径 canvas.drawPath(path, paint); // 填充路径 paint.style PaintingStyle.fill; paint.color Colors.purple.withOpacity(0.3); canvas.drawPath(path, paint); } override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }3. 渐变效果// 渐变绘制 class GradientPainter extends CustomPainter { override void paint(Canvas canvas, Size size) { // 线性渐变 final linearGradient LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Colors.blue, Colors.purple], ); final paint Paint() ..shader linearGradient.createShader( Rect.fromLTWH(0, 0, size.width, size.height), ); canvas.drawRect( Rect.fromLTWH(0, 0, size.width, size.height), paint, ); // 径向渐变 final radialGradient RadialGradient( center: Alignment.center, radius: 0.5, colors: [Colors.yellow, Colors.orange, Colors.red], stops: [0.3, 0.7, 1.0], ); paint.shader radialGradient.createShader( Rect.fromCircle( center: Offset(size.width / 2, size.height / 2), radius: 100, ), ); canvas.drawCircle( Offset(size.width / 2, size.height / 2), 100, paint, ); } override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }三、高级绘制技巧1. 动画绘制// 动画绘制 class AnimatedPainter extends CustomPainter { final Animationdouble animation; AnimatedPainter({required this.animation}) : super(repaint: animation); override void paint(Canvas canvas, Size size) { final paint Paint() ..color Colors.blue ..style PaintingStyle.fill; // 基于动画值计算位置 final center Offset( size.width / 2 size.width / 3 * animation.value * 2 - size.width / 3, size.height / 2, ); // 绘制移动的圆形 canvas.drawCircle(center, 50, paint); // 绘制轨迹 paint.color Colors.blue.withOpacity(0.3); paint.style PaintingStyle.stroke; paint.strokeWidth 2; canvas.drawCircle(center, 50, paint); } override bool shouldRepaint(covariant AnimatedPainter oldDelegate) { return animation ! oldDelegate.animation; } } // 使用动画绘制 class AnimatedPainterWidget extends StatefulWidget { override _AnimatedPainterWidgetState createState() _AnimatedPainterWidgetState(); } class _AnimatedPainterWidgetState extends StateAnimatedPainterWidget with SingleTickerProviderStateMixin { late AnimationController _controller; late Animationdouble _animation; override void initState() { super.initState(); _controller AnimationController( duration: Duration(seconds: 2), vsync: this, )..repeat(reverse: true); _animation CurvedAnimation( parent: _controller, curve: Curves.easeInOut, ); } override void dispose() { _controller.dispose(); super.dispose(); } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(动画绘制)), body: Center( child: CustomPaint( painter: AnimatedPainter(animation: _animation), size: Size(400, 400), ), ), ); } }2. 复杂图形绘制// 复杂图形绘制 class ComplexPainter extends CustomPainter { override void paint(Canvas canvas, Size size) { final center Offset(size.width / 2, size.height / 2); final radius 150.0; // 绘制多边形 final paint Paint() ..color Colors.blue ..style PaintingStyle.stroke ..strokeWidth 3; // 绘制六边形 final hexagonPath Path(); for (int i 0; i 6; i) { final angle (i * 60 - 90) * 3.14159 / 180; final x center.dx radius * cos(angle); final y center.dy radius * sin(angle); if (i 0) { hexagonPath.moveTo(x, y); } else { hexagonPath.lineTo(x, y); } } hexagonPath.close(); canvas.drawPath(hexagonPath, paint); // 填充多边形 paint.style PaintingStyle.fill; paint.color Colors.blue.withOpacity(0.2); canvas.drawPath(hexagonPath, paint); // 绘制星形 final starPaint Paint() ..color Colors.yellow ..style PaintingStyle.fill; final starPath Path(); for (int i 0; i 10; i) { final angle (i * 36 - 90) * 3.14159 / 180; final starRadius i % 2 0 ? radius * 0.5 : radius * 0.3; final x center.dx starRadius * cos(angle); final y center.dy starRadius * sin(angle); if (i 0) { starPath.moveTo(x, y); } else { starPath.lineTo(x, y); } } starPath.close(); canvas.drawPath(starPath, starPaint); } override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }3. 文字绘制// 文字绘制 class TextPainterExample extends CustomPainter { override void paint(Canvas canvas, Size size) { // 绘制文字 final textSpan TextSpan( text: Hello, CustomPainter!, style: TextStyle( color: Colors.black, fontSize: 24, fontWeight: FontWeight.bold, ), ); final textPainter TextPainter( text: textSpan, textAlign: TextAlign.center, textDirection: TextDirection.ltr, )..layout(minWidth: 0, maxWidth: size.width); final textOffset Offset( (size.width - textPainter.width) / 2, (size.height - textPainter.height) / 2, ); textPainter.paint(canvas, textOffset); // 绘制渐变文字 final gradient LinearGradient( colors: [Colors.blue, Colors.purple, Colors.pink], ); final gradientTextSpan TextSpan( text: Gradient Text, style: TextStyle( fontSize: 32, fontWeight: FontWeight.bold, foreground: Paint() ..shader gradient.createShader( Rect.fromLTWH(0, 0, size.width, 50), ), ), ); final gradientTextPainter TextPainter( text: gradientTextSpan, textAlign: TextAlign.center, textDirection: TextDirection.ltr, )..layout(minWidth: 0, maxWidth: size.width); final gradientTextOffset Offset( (size.width - gradientTextPainter.width) / 2, textOffset.dy textPainter.height 20, ); gradientTextPainter.paint(canvas, gradientTextOffset); } override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }四、实战案例1. 时钟绘制// 时钟绘制 class ClockPainter extends CustomPainter { override void paint(Canvas canvas, Size size) { final center Offset(size.width / 2, size.height / 2); final radius size.width / 2; // 绘制时钟外圆 final paint Paint() ..color Colors.grey[300]! ..style PaintingStyle.fill; canvas.drawCircle(center, radius, paint); // 绘制时钟边框 paint.style PaintingStyle.stroke; paint.color Colors.grey; paint.strokeWidth 4; canvas.drawCircle(center, radius - 2, paint); // 绘制刻度 paint.strokeWidth 2; paint.color Colors.black; for (int i 0; i 12; i) { final angle (i * 30 - 90) * 3.14159 / 180; final innerRadius radius - 20; final outerRadius radius - 10; final innerX center.dx innerRadius * cos(angle); final innerY center.dy innerRadius * sin(angle); final outerX center.dx outerRadius * cos(angle); final outerY center.dy outerRadius * sin(angle); canvas.drawLine( Offset(innerX, innerY), Offset(outerX, outerY), paint, ); } // 绘制当前时间 final now DateTime.now(); final hourAngle (now.hour % 12 * 30 now.minute * 0.5 - 90) * 3.14159 / 180; final minuteAngle (now.minute * 6 - 90) * 3.14159 / 180; final secondAngle (now.second * 6 - 90) * 3.14159 / 180; // 绘制时针 paint.color Colors.black; paint.strokeWidth 6; canvas.drawLine( center, Offset( center.dx (radius - 60) * cos(hourAngle), center.dy (radius - 60) * sin(hourAngle), ), paint, ); // 绘制分针 paint.strokeWidth 4; canvas.drawLine( center, Offset( center.dx (radius - 40) * cos(minuteAngle), center.dy (radius - 40) * sin(minuteAngle), ), paint, ); // 绘制秒针 paint.color Colors.red; paint.strokeWidth 2; canvas.drawLine( center, Offset( center.dx (radius - 30) * cos(secondAngle), center.dy (radius - 30) * sin(secondAngle), ), paint, ); // 绘制中心点 paint.style PaintingStyle.fill; paint.color Colors.black; canvas.drawCircle(center, 8, paint); paint.color Colors.red; canvas.drawCircle(center, 4, paint); } override bool shouldRepaint(covariant ClockPainter oldDelegate) { return true; } } // 时钟 Widget class ClockWidget extends StatefulWidget { override _ClockWidgetState createState() _ClockWidgetState(); } class _ClockWidgetState extends StateClockWidget { late Timer _timer; override void initState() { super.initState(); _timer Timer.periodic(Duration(seconds: 1), (_) { setState(() {}); }); } override void dispose() { _timer.cancel(); super.dispose(); } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(时钟)), body: Center( child: CustomPaint( painter: ClockPainter(), size: Size(300, 300), ), ), ); } }2. 粒子效果// 粒子类 class Particle { Offset position; Offset velocity; double radius; Color color; Particle({ required this.position, required this.velocity, required this.radius, required this.color, }); void update(Size size) { position velocity; // 边界检测 if (position.dx radius || position.dx size.width - radius) { velocity Offset(-velocity.dx, velocity.dy); } if (position.dy radius || position.dy size.height - radius) { velocity Offset(velocity.dx, -velocity.dy); } } } // 粒子绘制 class ParticlePainter extends CustomPainter { final ListParticle particles; ParticlePainter({required this.particles}); override void paint(Canvas canvas, Size size) { for (final particle in particles) { final paint Paint() ..color particle.color ..style PaintingStyle.fill; canvas.drawCircle(particle.position, particle.radius, paint); // 绘制光晕 paint.color particle.color.withOpacity(0.3); canvas.drawCircle(particle.position, particle.radius * 2, paint); } // 绘制连接线 final linePaint Paint() ..color Colors.blue.withOpacity(0.3) ..style PaintingStyle.stroke ..strokeWidth 1; for (int i 0; i particles.length; i) { for (int j i 1; j particles.length; j) { final distance (particles[i].position - particles[j].position).distance; if (distance 100) { linePaint.color Colors.blue.withOpacity((1 - distance / 100) * 0.3); canvas.drawLine( particles[i].position, particles[j].position, linePaint, ); } } } } override bool shouldRepaint(covariant ParticlePainter oldDelegate) { return true; } } // 粒子效果 Widget class ParticleWidget extends StatefulWidget { override _ParticleWidgetState createState() _ParticleWidgetState(); } class _ParticleWidgetState extends StateParticleWidget with SingleTickerProviderStateMixin { late ListParticle particles; late AnimationController _controller; override void initState() { super.initState(); // 初始化粒子 final random Random(); particles List.generate( 30, (index) Particle( position: Offset( random.nextDouble() * 400, random.nextDouble() * 400, ), velocity: Offset( (random.nextDouble() - 0.5) * 4, (random.nextDouble() - 0.5) * 4, ), radius: random.nextDouble() * 5 3, color: Color.fromARGB( 255, random.nextInt(256), random.nextInt(256), random.nextInt(256), ), ), ); _controller AnimationController( duration: Duration(seconds: 1), vsync: this, )..repeat(); _controller.addListener(() { setState(() { for (final particle in particles) { particle.update(Size(400, 400)); } }); }); } override void dispose() { _controller.dispose(); super.dispose(); } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(粒子效果)), body: Center( child: CustomPaint( painter: ParticlePainter(particles: particles), size: Size(400, 400), ), ), ); } }五、性能优化合理使用 shouldRepaint只在必要时重绘避免频繁创建对象重用 Paint 和 Path 对象使用 saveLayer在复杂绘制时使用测试性能在不同设备上测试绘制性能使用 RepaintBoundary减少不必要的重绘// 性能优化的 Painter class OptimizedPainter extends CustomPainter { final Paint _paint; final Path _path; OptimizedPainter() : _paint Paint(), _path Path(); override void paint(Canvas canvas, Size size) { // 重用 Paint 对象 _paint.color Colors.blue; _paint.style PaintingStyle.fill; // 绘制 canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), _paint); } override bool shouldRepaint(covariant OptimizedPainter oldDelegate) { return false; } }六、最佳实践保持简洁避免过度复杂的绘制逻辑有意义自定义绘制应该有明确的设计目的测试在不同设备上测试绘制效果性能注意绘制性能避免卡顿可维护性保持代码清晰易懂七、总结Flutter 自定义绘制是创建独特用户界面的强大工具它让我们可以在画布上自由创作。通过掌握 CustomPainter 的高级技巧我们可以创造出令人惊艳的视觉效果。作为一名 UI 匠人我建议在项目中合理使用自定义绘制让界面更加生动有趣。代码是画笔Canvas 是画布让我们用代码作画。#flutter #custom-painter #frontend #ui #drawing

更多文章