Flutter实现按钮触发不同书本翻页动画的技术问题
解决方案
核心问题在于你用Stack堆叠了所有动画,导致上层动画始终遮挡下层。正确的做法是只渲染当前激活的动画,而非同时显示所有动画。以下是优化后的实现:
修订代码
class OpeningBook extends StatefulWidget { const OpeningBook({super.key}); @override State<OpeningBook> createState() => _OpeningBookState(); } class _OpeningBookState extends State<OpeningBook> with TickerProviderStateMixin { late final AnimationController _animationController; String? _currentAnimationAsset; // 记录当前要播放的动画资源 bool _isNavigating = false; // 防止重复触发导航 @override void initState() { super.initState(); _animationController = AnimationController( duration: const Duration(milliseconds: 2000), vsync: this, ); // 监听动画状态,完成后跳转页面 _animationController.addStatusListener((status) { if (status == AnimationStatus.completed && !_isNavigating) { _isNavigating = true; _navigateToSubpage(); } else if (status == AnimationStatus.dismissed) { // 动画反向完成后,重置当前动画资源 setState(() => _currentAnimationAsset = null); } }); // 监听路由变化,返回首页时触发反向动画 GoRouter.of(context).addListener(_handleRouteChange); } // 处理路由切换,返回首页时播放反向动画 void _handleRouteChange() { final currentRoute = GoRouter.of(context).location; if (currentRoute == '/' && _currentAnimationAsset != null && _animationController.status != AnimationStatus.reverse && _animationController.status != AnimationStatus.forward) { _animationController.reverse(); } } // 根据当前动画资源跳转对应子页面 void _navigateToSubpage() { switch (_currentAnimationAsset) { case 'assets/animations/Calendar_Ani.json': context.goNamed(AppRoute.calendar.name); break; case 'assets/animations/LessonPrep_Ani.json': context.goNamed(AppRoute.weeks.name); break; // 添加另外3个动画对应的路由跳转逻辑 } // 延迟重置导航标记,避免重复触发 Future.delayed(const Duration(milliseconds: 500), () => _isNavigating = false); } @override void dispose() { _animationController.dispose(); GoRouter.of(context).removeListener(_handleRouteChange); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // 动画容器:只渲染当前激活的动画 Container( height: 600, width: 600, alignment: Alignment.center, child: _currentAnimationAsset != null ? Lottie.asset( _currentAnimationAsset!, controller: _animationController, ) : // 默认显示书本闭合状态(可替换为静态图或闭合动画帧) Lottie.asset( 'assets/animations/ClosedBook_Ani.json', repeat: false, ), ), // 导航按钮组 Column( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( onPressed: () { if (_animationController.status != AnimationStatus.forward && !_isNavigating) { setState(() { _currentAnimationAsset = 'assets/animations/Calendar_Ani.json'; }); _animationController.reset(); _animationController.forward(); } }, iconSize: 50, icon: const Icon(Icons.calendar_month), tooltip: context.loc.calendar, ), const SizedBox(height: 35), IconButton( onPressed: () { if (_animationController.status != AnimationStatus.forward && !_isNavigating) { setState(() { _currentAnimationAsset = 'assets/animations/LessonPrep_Ani.json'; }); _animationController.reset(); _animationController.forward(); } }, iconSize: 50, icon: const Icon(Icons.auto_stories), tooltip: context.loc.lessonPlanner, ), // 添加剩余3个按钮 ], ), ], ), ); } }
关键优化点
- 单控制器+动态切换资源:用一个
AnimationController控制所有动画,通过_currentAnimationAsset动态切换要播放的Lottie文件,避免堆叠遮挡。 - 动画状态监听:通过
addStatusListener处理动画完成后的跳转,替代原代码中不可靠的Future.delayed。 - 路由监听:监听GoRouter的路由变化,返回首页时自动触发反向动画,实现书本闭合效果。
- 防重复触发:通过
_isNavigating和控制器状态检查,避免用户重复点击按钮导致的动画混乱。
内容的提问来源于stack exchange,提问作者DanJayLee79
相关产品推荐
相关产品推荐

