Flutter中CustomPainter绘制傅里叶级数波形出现异常连线问题
傅里叶级数动画波形异常连线问题修复
问题描述
我正在还原傅里叶级数的首个动画效果,拖动滑块时波形原本能较好逼近方波,但突然出现异常连线导致画面混乱。以下是当前代码(Testing需作为MaterialApp的首页调用):
import 'package:flutter/material.dart'; import "dart:math"; class Testing extends StatefulWidget { const Testing({super.key}); @override State<Testing> createState() => _TestingState(); } class _TestingState extends State<Testing> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _animation; late double _value = 1; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: const Duration(seconds: 5)); _animation = Tween(begin: 2 * pi, end: 0.0).animate(_controller); _controller.forward(); _controller.addListener(() { if (_animation.isCompleted) { _controller.reset(); _controller.forward(); } setState(() {}); }); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ Slider( min: 1, max: 100, divisions: 100, value: _value, onChanged: (new_value) => _value = new_value, ), SizedBox( height: 500, width: MediaQuery.sizeOf(context).width, child: ClipRRect( child: CustomPaint( painter: Sky( angle: _animation.value, running: _controller.isAnimating, value: _value.toInt()), size: Size(MediaQuery.sizeOf(context).width, 500), ), )), ], )); } } List<Offset> wave = []; class Sky extends CustomPainter { bool running; double angle; int value; Sky({required this.angle, required this.running, required this.value}); @override void paint(Canvas canvas, Size size) { var paint = Paint() ..color = Colors.black ..strokeWidth = 1.0 ..style = PaintingStyle.stroke; double centerX = 100; double centerY = size.height / 2; canvas.translate(centerX, centerY); double circleRadius = 80 * (4 / (1 * pi)); double x1 = 0; double y1 = 0; for (int i = 0; i < value; i++) { double prevx = x1; double prevy = y1; int n = (i * 2) + 1; circleRadius = 80 * (4 / (n * pi)); x1 += circleRadius * cos(n*angle); y1 += circleRadius * sin(n*angle); canvas.drawCircle(Offset(prevx, prevy), circleRadius, paint); canvas.drawLine(Offset(prevx, prevy), Offset(x1, y1), paint); } double waveValue = running ? y1 : 0; wave.insert(0, Offset(240, y1)); if (running) { canvas.drawLine(Offset(x1, y1), Offset(240, y1), paint); } List<Offset> wave2 = wave.map((e) => e.translate(wave.indexOf(e).toDouble(), 0)).toList(); Path path = Path(); path.moveTo(wave2[0].dx, wave2[0].dy); for (int i = 1; i < wave2.length; i++) { path.lineTo(wave2[i].dx, wave2[i].dy); } for (int i = wave2.length-1; i > 1; i--) { path.lineTo(wave2[i].dx, wave2[i].dy); } canvas.drawPath(path, paint); if (wave.length > 1000) { wave.removeLast(); } } @override bool shouldRepaint(CustomPainter oldDelegate) { return true; } }
问题根源
- 全局wave列表状态污染:全局变量
wave会保留所有历史绘制数据,滑块调整时新的绘制逻辑会混入旧数据,导致异常连线。 - 波形平移逻辑错误:用
wave.indexOf(e)获取索引会因为重复坐标导致索引混乱,且每次绘制都平移所有点,会让波形出现无规律偏移。 - 路径绘制冗余:反向遍历绘制重复点,会产生多余的连线。
修复后的完整代码
import 'package:flutter/material.dart'; import "dart:math"; class Testing extends StatefulWidget { const Testing({super.key}); @override State<Testing> createState() => _TestingState(); } class _TestingState extends State<Testing> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _animation; late double _value = 1; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: const Duration(seconds: 5)); _animation = Tween(begin: 2 * pi, end: 0.0).animate(_controller); _controller.forward(); _controller.addListener(() { if (_animation.isCompleted) { _controller.reset(); _controller.forward(); } setState(() {}); }); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ Slider( min: 1, max: 100, divisions: 100, value: _value, onChanged: (new_value) => setState(() => _value = new_value), ), SizedBox( height: 500, width: MediaQuery.sizeOf(context).width, child: ClipRRect( child: CustomPaint( painter: Sky( angle: _animation.value, running: _controller.isAnimating, value: _value.toInt()), size: Size(MediaQuery.sizeOf(context).width, 500), ), )), ], )); } } class Sky extends CustomPainter { final bool running; final double angle; final int value; // 将wave改为类成员变量,避免全局状态污染 final List<Offset> _wave = []; // 记录波形X轴偏移量,保证点依次排列 int _waveXOffset = 0; Sky({required this.angle, required this.running, required this.value}); @override void paint(Canvas canvas, Size size) { var paint = Paint() ..color = Colors.black ..strokeWidth = 1.0 ..style = PaintingStyle.stroke; double centerX = 100; double centerY = size.height / 2; canvas.translate(centerX, centerY); double x1 = 0; double y1 = 0; for (int i = 0; i < value; i++) { double prevx = x1; double prevy = y1; int n = (i * 2) + 1; double circleRadius = 80 * (4 / (n * pi)); x1 += circleRadius * cos(n * angle); y1 += circleRadius * sin(n * angle); canvas.drawCircle(Offset(prevx, prevy), circleRadius, paint); canvas.drawLine(Offset(prevx, prevy), Offset(x1, y1), paint); } if (running) { // 绘制到波形区域的垂直线 canvas.drawLine(Offset(x1, y1), Offset(240, y1), paint); // 添加新波形点,X坐标基于偏移量递增 _wave.insert(0, Offset(240 + _waveXOffset, y1)); _waveXOffset++; // 限制波形点数量,防止内存占用过高 if (_wave.length > 300) { _wave.removeLast(); } } else { // 停止动画时清空波形 _wave.clear(); _waveXOffset = 0; } // 绘制连续波形路径 if (_wave.length > 1) { Path path = Path(); path.moveTo(_wave.first.dx, _wave.first.dy); for (int i = 1; i < _wave.length; i++) { path.lineTo(_wave[i].dx, _wave[i].dy); } canvas.drawPath(path, paint); } } @override bool shouldRepaint(CustomPainter oldDelegate) { return true; } }
关键修复说明
- 移除全局wave变量:将波形数据改为
Sky类的成员变量,滑块调整时会创建新的Sky实例,自动重置波形数据,避免新旧数据混合。 - 修正波形坐标逻辑:通过
_waveXOffset记录当前波形的X轴位置,每次添加新点时直接设置正确的X坐标,无需重复平移所有点。 - 简化路径绘制:只从左到右连接波形点,移除冗余的反向绘制逻辑。
- 滑块回调添加setState:原代码滑块调整后未触发UI更新,现在添加
setState实现实时刷新。
内容的提问来源于stack exchange,提问作者Sanchit Batra
相关产品推荐
相关产品推荐

