Flutter实现Widget切换时的渐隐渐显过渡动画问题
嘿,这个Widget切换的渐隐渐显顺序动画问题我刚好多有研究,给你两个实用的解决方案,你可以根据需求选:
方案一:用AnimatedOpacity分步控制动画(直观易定制)
这个方案的思路是先让加载组件渐隐,等动画完成后再让内容组件渐显,通过状态变量和Future.delayed来精准控制时序。
修改后的完整代码如下:
import 'package:flutter/material.dart'; class MyWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _MyWidgetState(); } } class _MyWidgetState extends State<MyWidget> { bool _isLoading = true; bool _showContent = false; double _loadingOpacity = 1.0; double _contentOpacity = 0.0; // 统一设置动画时长,方便后续调整 final _animationDuration = Duration(milliseconds: 500); @override Widget build(BuildContext context) { return Container( color: Theme.of(context).scaffoldBackgroundColor, child: Stack( alignment: Alignment.center, children: [ // 加载组件:控制渐隐动画 if (_isLoading) AnimatedOpacity( opacity: _loadingOpacity, duration: _animationDuration, child: SizedBox( width: 24, height: 24, child: GestureDetector( onTap: _toggleWidget, child: CircularProgressIndicator(), ), ), ), // 内容组件:控制渐显动画 if (_showContent) AnimatedOpacity( opacity: _contentOpacity, duration: _animationDuration, child: GestureDetector( onTap: _toggleWidget, child: Text( "WELCOME", style: TextStyle(fontSize: 24), ), ), ), ], ), ); } void _toggleWidget() async { if (_isLoading) { // 第一步:让加载组件渐隐 setState(() { _loadingOpacity = 0.0; }); // 等待渐隐动画完成 await Future.delayed(_animationDuration); // 第二步:切换状态,显示内容组件并渐显 setState(() { _isLoading = false; _showContent = true; _contentOpacity = 1.0; }); } else { // 反向切换:先让内容组件渐隐 setState(() { _contentOpacity = 0.0; }); await Future.delayed(_animationDuration); // 再显示加载组件并渐显 setState(() { _showContent = false; _isLoading = true; _loadingOpacity = 1.0; }); } } }
方案二:用AnimatedSwitcher实现顺序动画(简洁高效)
AnimatedSwitcher是Flutter专门用于Widget切换动画的原生组件,我们可以通过自定义transitionBuilder来控制动画时序,实现先隐后显的效果:
import 'package:flutter/material.dart'; class MyWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _MyWidgetState(); } } class _MyWidgetState extends State<MyWidget> { bool _isLoading = true; // 总动画时长:前半段旧组件渐隐,后半段新组件渐显 final _totalDuration = Duration(milliseconds: 1000); @override Widget build(BuildContext context) { return Container( color: Theme.of(context).scaffoldBackgroundColor, child: AnimatedSwitcher( duration: _totalDuration, // 自定义动画时序:前50%时间旧组件渐隐,后50%时间新组件渐显 transitionBuilder: (child, animation) { final isLoadingWidget = child.key == const ValueKey('loading'); // 旧组件(加载)的渐隐动画:0-50%时间完成 final outAnimation = Tween<double>(begin: 1.0, end: 0.0).animate( CurvedAnimation( parent: animation, curve: const Interval(0.0, 0.5, curve: Curves.easeOut), ), ); // 新组件(内容)的渐显动画:50%-100%时间完成 final inAnimation = Tween<double>(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: animation, curve: const Interval(0.5, 1.0, curve: Curves.easeIn), ), ); return FadeTransition( opacity: isLoadingWidget ? outAnimation : inAnimation, child: child, ); }, // 必须给每个切换的Widget设置唯一key,AnimatedSwitcher才能识别切换动作 child: _isLoading ? SizedBox( key: const ValueKey('loading'), width: 24, height: 24, child: GestureDetector( onTap: _toggleWidget, child: CircularProgressIndicator(), ), ) : GestureDetector( key: const ValueKey('content'), onTap: _toggleWidget, child: const Text( "WELCOME", style: TextStyle(fontSize: 24), ), ), ), ); } void _toggleWidget() { setState(() { _isLoading = !_isLoading; }); } }
两个方案的对比
- 方案一更适合需要精细控制动画步骤的场景(比如中间要插入其他业务逻辑),代码可读性高,新手容易理解和修改。
- 方案二更简洁,利用Flutter原生组件的能力,适合大多数常规的顺序切换需求,代码量更少,维护成本低。
内容的提问来源于stack exchange,提问作者DJafari
相关产品推荐
相关产品推荐

