Flutter动画按钮子组件切换失效:需二次触发的解决问询
问题分析与解决方案
你的问题核心在于状态更新没有触发UI重建,以及动画与状态切换的时序匹配问题。以下是具体修复方案:
1. 核心问题拆解
- 用静态类
_LocalVariables存储isLoading:即便值改变,StatelessWidget不会自动重建,UI无法更新;就算改成StatefulWidget,没调用setState也不会触发刷新。 - 动画回调仅修改静态变量,未触发父组件的状态更新,导致首次切换时UI无变化,第二次触发才可能因其他逻辑间接触发重建。
2. 修改后的代码实现
动画组件类(优化时序)
typedef LoadingStatusCallback = void Function(bool isLoading); class AMASOpacityAndScale extends StatefulWidget { const AMASOpacityAndScale( {super.key, required this.animationDurationInMiliSec, required this.child, required this.loadingStatusCallback}); final int animationDurationInMiliSec; final Widget child; final LoadingStatusCallback loadingStatusCallback; @override State<AMASOpacityAndScale> createState() => _AMASOpacityAndScaleState(); } class _AMASOpacityAndScaleState extends State<AMASOpacityAndScale> with TickerProviderStateMixin { late final AnimationController _controller = AnimationController( vsync: this, duration: Duration(milliseconds: widget.animationDurationInMiliSec)); final Curve _curve = Curves.easeIn; late final Animation<double> _animation = CurvedAnimation(curve: _curve, parent: _controller); bool _isLoading = false; void _animate() async { // 先执行淡出缩小动画 await _controller.reverse(); // 切换状态并通知父组件 _isLoading = !_isLoading; widget.loadingStatusCallback(_isLoading); // 等待父组件完成UI重建后,再执行淡入放大动画 WidgetsBinding.instance.addPostFrameCallback((_) { _controller.forward(); }); } @override void didUpdateWidget(covariant AMASOpacityAndScale oldWidget) { _animate(); super.didUpdateWidget(oldWidget); } @override void initState() { _controller.forward(); super.initState(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _animation, builder: (context, child) => Opacity( opacity: _controller.value, child: ScaleTransition( scale: _animation, child: widget.child, )), ); } }
按钮组件(改为StatefulWidget管理状态)
class AMASButton extends StatefulWidget { final String? text; final Color? textColor; final double? fontSize; final String? fontFamily; final FontWeight? fontWeight; final Color? gradientBackgroundColor; final bool hasOutLines; final double? height; const AMASButton({ super.key, this.text, this.textColor, this.fontSize, this.fontFamily, this.fontWeight, this.gradientBackgroundColor, this.hasOutLines = false, this.height, }); @override State<AMASButton> createState() => _AMASButtonState(); } class _AMASButtonState extends State<AMASButton> { bool _isLoading = false; @override Widget build(BuildContext context) { return GestureDetector( // 点击触发动画 onTap: () { setState(() {}); }, child: AMASOpacityAndScale( animationDurationInMiliSec: 200, loadingStatusCallback: (isLoading) { // 触发按钮组件重建,切换子组件 setState(() { _isLoading = isLoading; }); }, child: !_isLoading ? Text( widget.text ?? 'AMASButton', style: TextStyle( color: widget.textColor ?? (widget.gradientBackgroundColor == null && !widget.hasOutLines ? Colors.white : Colors.black), fontSize: widget.fontSize, fontFamily: widget.fontFamily, fontWeight: widget.fontWeight), ) : SizedBox( height: widget.height != null ? (widget.height! - widget.height! / 2.2) : 50 - 22, width: widget.height != null ? (widget.height! - widget.height! / 2.2) : 50 - 50 / 2.2, child: CircularProgressIndicator( strokeWidth: 2, color: widget.textColor ?? Colors.white, ), ), ), ); } }
3. 关键修改说明
- 替换静态状态:用StatefulWidget的内部状态
_isLoading替代静态类,确保状态变化能触发UI重建。 - 触发父组件刷新:在
loadingStatusCallback中调用setState,让按钮组件重建,从而切换文本和加载指示器。 - 动画时序优化:用
WidgetsBinding.instance.addPostFrameCallback确保父组件完成UI更新后,再执行淡入放大动画,避免动画执行时子组件尚未切换。 - 触发动画逻辑:点击按钮时调用
setState(),触发AMASOpacityAndScale的didUpdateWidget方法,启动动画流程。
内容的提问来源于stack exchange,提问作者Amir Mohammad Ahmadian
相关产品推荐
相关产品推荐

