You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
  }
}

关键改动说明

  1. 替换动画启动方法:把animController.repeat(reverse: true)改成animController.forward(),forward()会让动画从begin值(0.0)播放到end值(math.pi,对应100%),播放完成后自动停止。
  2. 添加控制器销毁逻辑:在dispose()方法中销毁AnimationController,避免内存泄漏,这是Flutter动画开发的最佳实践。
  3. 优化重绘判断:修改shouldRepaint方法,只有当arc、progressColor或isBackground发生变化时才重绘,避免不必要的性能消耗。
  4. 小细节修正:把原代码中的userCenter修正为useCenter,这是drawArc方法的正确参数名,原代码属于笔误(不影响功能但规范代码)。

这样修改后,你的进度指示器就会从0%平滑播放到100%,然后保持在100%的状态,不会再循环了。

内容的提问来源于stack exchange,提问作者rameez khan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 03:24:07