Flutter:如何停止CustomPainter实现的进度指示器动画
解决进度指示器动画循环问题
你的进度条动画持续循环的核心原因是调用了animController.repeat(reverse: true),这个方法会让动画在begin和end值之间反复来回播放。要让动画只播放一次到100%后停止,我们只需要调整动画的启动方式,同时可以优化一些细节提升性能。
修改步骤与完整代码
import 'dart:math' as math; import 'package:flutter/material.dart'; final Gradient gradient = LinearGradient( colors: <Color>[ Colors.greenAccent.withOpacity(1.0), Colors.yellowAccent.withOpacity(1.0), Colors.redAccent.withOpacity(1.0), ], stops: [ 0.0, 0.5, 1.0, ], ); class CircularArc extends StatefulWidget { const CircularArc({ Key? key, }) : super(key: key); @override _CircularArcState createState() => _CircularArcState(); } class _CircularArcState extends State<CircularArc> with SingleTickerProviderStateMixin { Animation<double>? animation; AnimationController? animController; @override void initState() { super.initState(); animController = AnimationController(duration: const Duration(seconds: 3), vsync: this); final curvedAnimation = CurvedAnimation(parent: animController!, curve: Curves.easeInOutCubic); animation = Tween<double>(begin: 0.0, end: math.pi).animate(curvedAnimation) ..addListener(() { setState(() {}); }); // 关键改动:用forward()替代repeat(),动画只播放一次到100%后停止 animController?.forward(); } @override void dispose() { // 记得销毁控制器,避免内存泄漏 animController?.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container( child: Stack( children: [ CustomPaint( size: const Size(300, 300), painter: ProgressArc(null, Colors.black54, true), ), CustomPaint( size: const Size(300, 300), painter: ProgressArc(animation?.value, Colors.redAccent, false), ), Positioned( top: 120, left: 130, child: Text( "${((animation?.value ?? 0) / math.pi * 100).round()}%", style: const TextStyle(color: Colors.red, fontSize: 30) ) ) ], ), ); } } class ProgressArc extends CustomPainter { final bool isBackground; final double? arc; final Color progressColor; ProgressArc(this.arc, this.progressColor, this.isBackground); @override void paint(Canvas canvas, Size size) { final rect = const Rect.fromLTRB(0, 0, 300, 300); final startAngle = -math.pi; final sweepAngle = arc ?? math.pi; final useCenter = false; // 变量名修正,原userCenter是笔误 final paint = Paint() ..strokeCap = StrokeCap.round ..color = progressColor ..style = PaintingStyle.stroke ..strokeWidth = 20; if (!isBackground) { paint.shader = gradient.createShader(rect); } canvas.drawArc(rect, startAngle, sweepAngle, useCenter, paint); } @override bool shouldRepaint(covariant ProgressArc oldDelegate) { // 优化:只有当参数变化时才重绘,提升性能 return oldDelegate.arc != arc || oldDelegate.progressColor != progressColor || oldDelegate.isBackground != isBackground; } }
关键改动说明
- 替换动画启动方法:把
animController.repeat(reverse: true)改成animController.forward(),forward()会让动画从begin值(0.0)播放到end值(math.pi,对应100%),播放完成后自动停止。 - 添加控制器销毁逻辑:在
dispose()方法中销毁AnimationController,避免内存泄漏,这是Flutter动画开发的最佳实践。 - 优化重绘判断:修改
shouldRepaint方法,只有当arc、progressColor或isBackground发生变化时才重绘,避免不必要的性能消耗。 - 小细节修正:把原代码中的
userCenter修正为useCenter,这是drawArc方法的正确参数名,原代码属于笔误(不影响功能但规范代码)。
这样修改后,你的进度指示器就会从0%平滑播放到100%,然后保持在100%的状态,不会再循环了。
内容的提问来源于stack exchange,提问作者rameez khan
相关产品推荐
相关产品推荐

