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

Flutter StatefulWidget中setState未触发问题:按钮转进度条动画实现异常

问题分析与修复方案

我看了你的代码,发现几个关键问题导致点击按钮后没有预期的动画效果,咱们一个个来解决:

1. 动画控制器的错误使用

你在animateButton里把AnimationController定义成了局部变量,而且把controller.forward()放在了addListener回调里——这会导致每次动画帧触发时都重复启动动画,直接打乱了动画逻辑,而且局部控制器会在方法执行完后被回收,动画根本没法正常运行。

修复:
把AnimationController改成类的成员变量,在initState里初始化,并且把controller.forward()移到listener外面:

late AnimationController _controller; // 改成类成员

@override
void initState() {
  super.initState();
  _controller = AnimationController(
    duration: const Duration(milliseconds: 500), // 这里把microseconds改成milliseconds,500微秒太快了肉眼看不到
    vsync: this,
  );
}

@override
void dispose() {
  _controller.dispose(); // 别忘了销毁控制器,避免内存泄漏
  super.dispose();
}

2. 状态更新未触发UI重建

你在onPressed里直接修改了_isPressed = true和state变量,但没有调用setState(),这会导致Flutter不知道需要更新UI,按钮的状态变化不会被渲染出来。

修复:
在修改状态后调用setState():

onPressed: () {
  setState(() {
    _isPressed = true;
    if (state == 0) {
      animateButton();
    }
  });
},

3. 动画监听逻辑错误

原来的addListener里的逻辑有问题,而且没有正确更新state变量来标记动画阶段,另外初始宽度的获取可以更稳妥一点。

修复后的animateButton方法:

void animateButton() {
  double initialWidth = globalKey.currentContext!.size!.width;
  // 用Tween来管理宽度变化,更清晰
  final widthTween = Tween<double>(begin: initialWidth, end: 48.0);
  
  _animation = widthTween.animate(_controller)
    ..addListener(() {
      setState(() {
        width = _animation.value;
      });
    })
    ..addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        // 动画完成后可以更新state,比如标记为加载中
        setState(() {
          state = 1;
        });
        // 这里可以后续添加加载完成后的逻辑,比如恢复按钮或者显示成功状态
      }
    });
  
  _controller.forward();
}

4. 按钮内容未随状态变化

当前按钮一直显示"SignIn",动画完成后应该切换成加载指示器之类的内容,这样用户能看到状态变化。

修复:
修改ElevatedButton的child,根据state显示不同内容:

child: state == 0 
    ? Text(
        "SignIn",
        style: TextStyle(
          fontSize: AppFont.appFontSize
        ),
      )
    : const SizedBox(
        width: 20,
        height: 20,
        child: CircularProgressIndicator(
          color: Colors.white,
          strokeWidth: 2,
        ),
      ),

完整修复后的代码

class ProgressButton extends StatefulWidget {
  @override
  ProgressButtonState createState() => ProgressButtonState();
}

class ProgressButtonState extends State<ProgressButton> with SingleTickerProviderStateMixin {
  bool _isPressed = false;
  int state = 0;
  late Animation<double> _animation;
  late AnimationController _controller;
  GlobalKey globalKey = GlobalKey();
  double width = 80;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 500),
      vsync: this,
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return PhysicalModel(
      borderRadius: BorderRadius.circular(30.0),
      color: Colors.transparent,
      child: Container(
        width: width,
        child: ElevatedButton(
          key: globalKey,
          onPressed: () {
            setState(() {
              _isPressed = true;
              if (state == 0) {
                animateButton();
              }
            });
          },
          child: state == 0 
              ? Text(
                  "SignIn",
                  style: TextStyle(
                    fontSize: AppFont.appFontSize
                  ),
                )
              : const SizedBox(
                  width: 20,
                  height: 20,
                  child: CircularProgressIndicator(
                    color: Colors.white,
                    strokeWidth: 2,
                  ),
                ),
          style: ElevatedButton.styleFrom(
            padding: const EdgeInsets.fromLTRB(0, 10, 0, 10),
            primary: AppColors.appFirstColor,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(30.0)
            )
          ),
        ),
      )
    );
  }

  void animateButton() {
    double initialWidth = globalKey.currentContext!.size!.width;
    final widthTween = Tween<double>(begin: initialWidth, end: 48.0);
    
    _animation = widthTween.animate(_controller)
      ..addListener(() {
        setState(() {
          width = _animation.value;
        });
      })
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          setState(() {
            state = 1;
          });
          // 这里可以添加你的业务逻辑,比如登录请求,完成后可以重置按钮状态
          // Future.delayed(const Duration(seconds: 2), () {
          //   setState(() {
          //     state = 0;
          //     _controller.reset();
          //     width = initialWidth;
          //   });
          // });
        }
      });
    
    _controller.forward();
  }
}

额外提示

  • 记得在dispose方法里销毁AnimationController,避免内存泄漏
  • 如果需要在加载完成后恢复按钮状态,可以在addStatusListener里用Future.delayed模拟异步操作,然后重置控制器和状态
  • 动画时长改成milliseconds而不是microseconds,500毫秒的动画速度更符合用户预期

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 19:52:47