Flutter 自定义绘制:创建精美的自定义图形

张开发
2026/6/12 9:27:46 15 分钟阅读
Flutter 自定义绘制:创建精美的自定义图形
Flutter 自定义绘制创建精美的自定义图形掌握 Flutter CustomPainter 的核心概念和实战技巧。一、自定义绘制的重要性作为一名追求像素级还原的 UI 匠人我深知自定义绘制在 Flutter 开发中的重要性。通过 CustomPainter我们可以创建各种复杂的图形、动画和视觉效果实现设计稿中的每一个细节。从简单的几何图形到复杂的路径绘制自定义绘制为我们提供了无限的创意空间。二、基本概念1. CustomPainter 类import package:flutter/material.dart; class MyCustomPainter extends CustomPainter { override void paint(Canvas canvas, Size size) { // 绘制逻辑 } override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } } class CustomPainterExample extends StatelessWidget { override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(CustomPainter 示例)), body: Center( child: Container( width: 300, height: 300, child: CustomPaint( painter: MyCustomPainter(), ), ), ), ); } }2. Canvas 和 Paintclass BasicShapesPainter extends CustomPainter { override void paint(Canvas canvas, Size size) { final paint Paint() ..color Colors.blue ..strokeWidth 2 ..style PaintingStyle.fill; // 绘制圆形 canvas.drawCircle( Offset(size.width / 2, size.height / 2), 50, paint, ); // 绘制矩形 paint.color Colors.red; canvas.drawRect( Rect.fromLTWH(50, 50, 100, 100), paint, ); // 绘制线条 paint.color Colors.green; paint.style PaintingStyle.stroke; canvas.drawLine( Offset(0, 0), Offset(size.width, size.height), paint, ); } override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }三、高级绘制技巧1. 路径绘制class PathPainter extends CustomPainter { override void paint(Canvas canvas, Size size) { final paint Paint() ..color Colors.purple ..strokeWidth 3 ..style PaintingStyle.stroke; final path Path(); path.moveTo(50, 50); path.lineTo(250, 50); path.quadraticBezierTo(250, 150, 150, 150); path.lineTo(150, 250); path.close(); canvas.drawPath(path, paint); } override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }2. 渐变和阴影class GradientPainter extends CustomPainter { override void paint(Canvas canvas, Size size) { // 线性渐变 final linearGradient LinearGradient( colors: [Colors.blue, Colors.purple], begin: Alignment.topLeft, end: Alignment.bottomRight, ); final paint Paint() ..shader linearGradient.createShader( Rect.fromLTWH(0, 0, size.width, size.height), ); // 绘制带阴影的圆形 final shadowPaint Paint() ..color Colors.black.withOpacity(0.3) ..maskFilter MaskFilter.blur(BlurStyle.normal, 10); canvas.drawCircle( Offset(size.width / 2, size.height / 2), 80, shadowPaint, ); canvas.drawCircle( Offset(size.width / 2, size.height / 2), 80, paint, ); } override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }3. 文本绘制class TextPainterExample extends CustomPainter { override void paint(Canvas canvas, Size size) { final textSpan TextSpan( text: Flutter CustomPainter, style: TextStyle( color: Colors.black, fontSize: 24, fontWeight: FontWeight.bold, ), ); final textPainter TextPainter( text: textSpan, textDirection: TextDirection.ltr, )..layout( minWidth: 0, maxWidth: size.width, ); textPainter.paint( canvas, Offset( (size.width - textPainter.width) / 2, (size.height - textPainter.height) / 2, ), ); } override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }四、动画绘制1. 简单动画class AnimatedPainter extends CustomPainter { final double progress; AnimatedPainter(this.progress); override void paint(Canvas canvas, Size size) { final paint Paint() ..color Colors.blue ..strokeWidth 4 ..style PaintingStyle.stroke; final center Offset(size.width / 2, size.height / 2); final radius size.width / 3; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), -pi / 2, 2 * pi * progress, false, paint, ); } override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } } class AnimatedPainterExample extends StatefulWidget { override _AnimatedPainterExampleState createState() _AnimatedPainterExampleState(); } class _AnimatedPainterExampleState extends StateAnimatedPainterExample 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 Scaffold( appBar: AppBar(title: Text(动画绘制示例)), body: Center( child: Container( width: 300, height: 300, child: AnimatedBuilder( animation: _animation, builder: (context, child) { return CustomPaint( painter: AnimatedPainter(_animation.value), ); }, ), ), ), ); } override void dispose() { _controller.dispose(); super.dispose(); } }五、实战案例1. 自定义图表class ChartPainter extends CustomPainter { final Listdouble data; ChartPainter(this.data); override void paint(Canvas canvas, Size size) { if (data.isEmpty) return; final paint Paint() ..color Colors.blue ..strokeWidth 2 ..style PaintingStyle.stroke; final maxValue data.reduce((a, b) a b ? a : b); final minValue data.reduce((a, b) a b ? a : b); final valueRange maxValue - minValue; final path Path(); for (int i 0; i data.length; i) { final x (i / (data.length - 1)) * size.width; final y size.height - ((data[i] - minValue) / valueRange) * size.height; if (i 0) { path.moveTo(x, y); } else { path.lineTo(x, y); } } canvas.drawPath(path, paint); // 绘制网格线 final gridPaint Paint() ..color Colors.grey.withOpacity(0.3) ..strokeWidth 1 ..style PaintingStyle.stroke; // 水平网格线 for (int i 0; i 5; i) { final y (i / 5) * size.height; canvas.drawLine(Offset(0, y), Offset(size.width, y), gridPaint); } // 垂直网格线 for (int i 0; i 5; i) { final x (i / 5) * size.width; canvas.drawLine(Offset(x, 0), Offset(x, size.height), gridPaint); } } override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } } class ChartExample extends StatelessWidget { final Listdouble data [10, 20, 15, 25, 30, 20, 35, 25, 40, 30]; override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(自定义图表)), body: Center( child: Container( width: 300, height: 300, child: CustomPaint( painter: ChartPainter(data), ), ), ), ); } }2. 自定义仪表盘class GaugePainter extends CustomPainter { final double value; // 0-1 GaugePainter(this.value); 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.withOpacity(0.2) ..strokeWidth 20 ..style PaintingStyle.stroke; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), pi * 1.25, pi * 1.5, false, backgroundPaint, ); // 绘制进度圆弧 final progressPaint Paint() ..color Colors.blue ..strokeWidth 20 ..style PaintingStyle.stroke ..strokeCap StrokeCap.round; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), pi * 1.25, pi * 1.5 * value, false, progressPaint, ); // 绘制中心文本 final textSpan TextSpan( text: ${(value * 100).toStringAsFixed(0)}%, style: TextStyle( color: Colors.black, fontSize: 32, fontWeight: FontWeight.bold, ), ); final textPainter TextPainter( text: textSpan, textDirection: TextDirection.ltr, )..layout( minWidth: 0, maxWidth: size.width, ); textPainter.paint( canvas, Offset( (size.width - textPainter.width) / 2, (size.height - textPainter.height) / 2, ), ); } override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } } class GaugeExample extends StatefulWidget { override _GaugeExampleState createState() _GaugeExampleState(); } class _GaugeExampleState extends StateGaugeExample { double _value 0.5; override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(自定义仪表盘)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 300, height: 300, child: CustomPaint( painter: GaugePainter(_value), ), ), SizedBox(height: 20), Slider( value: _value, onChanged: (newValue) { setState(() { _value newValue; }); }, min: 0, max: 1, ), ], ), ), ); } }六、性能优化合理使用 shouldRepaint只在需要重绘时返回 true缓存绘制结果对于静态内容使用 RepaintBoundary避免在 paint 方法中创建对象将 Paint 对象等创建在构造函数中使用 saveLayer 谨慎saveLayer 会创建新的绘制层可能影响性能测试性能在不同设备上测试绘制性能七、最佳实践模块化将不同的绘制逻辑分离到不同的 CustomPainter 类中参数化通过构造函数传递参数使绘制逻辑更加灵活组合使用结合 AnimationController 创建动态效果测试在不同设备和屏幕尺寸上测试绘制效果文档为复杂的绘制逻辑添加注释CustomPainter 是 Flutter 中实现自定义视觉效果的强大工具让我们能够创建出独特的 UI 组件。#flutter #custom-painter #canvas #animation #ui

更多文章