Flutter底部导航栏页面动态可变水平退出过渡动画实现方法
解决方案
最佳实现方案(推荐)
你的需求刚好匹配Flutter原生PageView组件的能力,完全不需要自定义复杂的路由动画,代码量最少、性能最优:
class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { final PageController _pageController = PageController(initialPage: 1); int _currentIndex = 1; void _onTabTapped(int index) { // 点击底部导航时触发PageView的横向切换动画 _pageController.animateToPage( index, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); setState(() => _currentIndex = index); } @override Widget build(BuildContext context) { return Scaffold( // PageView天生是横向排列的子页面,自带横向滑动切换效果 body: PageView( controller: _pageController, // 禁止手动滑动,仅通过底部导航控制切换,不需要可以删掉这行 physics: const NeverScrollableScrollPhysics(), children: const [FirstPage(), SecondPage(), ThirdPage()], ), bottomNavigationBar: BottomNavigationBar( currentIndex: _currentIndex, onTap: _onTabTapped, items: const [ BottomNavigationBarItem(icon: Icon(Icons.home), label: "第一页"), BottomNavigationBarItem(icon: Icon(Icons.business), label: "第二页"), BottomNavigationBarItem(icon: Icon(Icons.school), label: "第三页"), ], ), ); } }
如果需要保留每个页面的状态不被切换时重建,只需要给对应页面的State混入AutomaticKeepAliveClientMixin即可。
次选方案:AnimatedSwitcher自定义过渡
如果你的场景需要更灵活的自定义效果、或者tab数量不固定,可以用AnimatedSwitcher手动控制切换动画:
class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { int _currentIndex = 1; int _lastIndex = 1; final List<Widget> _pages = const [FirstPage(), SecondPage(), ThirdPage()]; void _onTabTapped(int index) { setState(() { _lastIndex = _currentIndex; _currentIndex = index; }); } @override Widget build(BuildContext context) { return Scaffold( body: AnimatedSwitcher( duration: const Duration(milliseconds: 300), // 把新旧页面放在Stack里实现同时滑动效果 layoutBuilder: (currentChild, previousChildren) { return Stack( fit: StackFit.expand, children: [ ...previousChildren, if (currentChild != null) currentChild, ], ); }, transitionBuilder: (child, animation) { // 计算滑动方向:index变大新页面从右侧进,反之从左侧进 final direction = _currentIndex - _lastIndex; return SlideTransition( position: Tween<Offset>( begin: Offset(direction > 0 ? 1 : -1, 0), end: Offset.zero, ).animate(CurvedAnimation(parent: animation, curve: Curves.easeInOut)), child: child, ); }, child: KeyedSubtree( key: ValueKey(_currentIndex), child: _pages[_currentIndex], ), ), bottomNavigationBar: BottomNavigationBar( currentIndex: _currentIndex, onTap: _onTabTapped, items: const [ BottomNavigationBarItem(icon: Icon(Icons.home), label: "第一页"), BottomNavigationBarItem(icon: Icon(Icons.business), label: "第二页"), BottomNavigationBarItem(icon: Icon(Icons.school), label: "第三页"), ], ), ); } }
原路由方案的问题说明
你之前的代码错误点在于:两个SlideTransition嵌套后所有动画都作用在新页面上,完全没有控制旧页面的退场效果。而且Flutter原生路由体系中,旧页面的退场动画是在旧路由创建时定义的,无法动态根据后续跳转目标修改,实现你要的可变退场效果成本很高,完全不如前面两种方案高效。
如果你必须保留嵌套Navigator的独立路由栈能力,可以参考成熟的路由库(如GoRouter)的自定义过渡能力,在每次切换tab传入方向参数时,同步控制新旧页面的滑动偏移即可。
内容的提问来源于stack exchange,提问作者maejoh
相关产品推荐
相关产品推荐

