go_router中StatefulShellRoute搭配CustomTransitionPage无效问题
解决GoRouter StatefulShellRoute Tab切换时CustomTransitionPage不生效的问题
你遇到的问题根源在于StatefulShellRoute.indexedStack的底层实现是用IndexedStack管理各个分支的Navigator,切换Tab时只是改变IndexedStack的显示索引,并不会触发路由页面的push/pop操作,所以你在GoRoute或StatefulShellRoute的pageBuilder中定义的CustomTransitionPage不会被触发——因为根本没有发生路由页面的跳转动作。
要实现Tab切换时的过渡动画,你不需要依赖CustomTransitionPage,而是直接在承载navigationShell的父组件中添加动画逻辑,比如使用AnimatedSwitcher包裹navigationShell,在切换Tab时触发过渡效果。
修改后的完整代码
import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; final _rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root'); final _sectionANavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'sectionANav'); void main() { runApp(NestedTabNavigationExampleApp()); } class NestedTabNavigationExampleApp extends StatelessWidget { NestedTabNavigationExampleApp({super.key}); final GoRouter _router = GoRouter( navigatorKey: _rootNavigatorKey, initialLocation: '/a', routes: <RouteBase>[ StatefulShellRoute.indexedStack( // 恢复为builder,shell过渡交给AnimatedSwitcher处理 builder: (context, state, navigationShell) { return ScaffoldWithNavBar(navigationShell: navigationShell); }, branches: <StatefulShellBranch>[ StatefulShellBranch( navigatorKey: _sectionANavigatorKey, routes: <RouteBase>[ GoRoute( path: '/a', builder: (context, state) => const RootScreen(label: 'A'), ), ], ), StatefulShellBranch( routes: <RouteBase>[ GoRoute( path: '/b', builder: (context, state) => const RootScreen(label: 'B'), ), ], ), StatefulShellBranch( routes: <RouteBase>[ GoRoute( path: '/c', builder: (context, state) => const RootScreen(label: 'C'), ), ], ), ], ), ], ); @override Widget build(BuildContext context) { return MaterialApp.router( title: 'Flutter Demo', theme: ThemeData(primarySwatch: Colors.blue), routerConfig: _router, ); } } class ScaffoldWithNavBar extends StatelessWidget { const ScaffoldWithNavBar({ required this.navigationShell, Key? key, }) : super(key: key ?? const ValueKey<String>('ScaffoldWithNavBar')); final StatefulNavigationShell navigationShell; @override Widget build(BuildContext context) { return Scaffold( // 用AnimatedSwitcher实现Tab切换动画 body: AnimatedSwitcher( duration: const Duration(milliseconds: 300), transitionBuilder: (child, animation) { // 自定义过渡效果,可替换为平移、缩放等动画 return FadeTransition( opacity: animation, child: child, ); }, // 给每个Tab的shell设置唯一key,确保AnimatedSwitcher识别组件变化 child: KeyedSubtree( key: ValueKey(navigationShell.currentIndex), child: navigationShell, ), ), bottomNavigationBar: BottomNavigationBar( items: const <BottomNavigationBarItem>[ BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Section A'), BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Section B'), BottomNavigationBarItem(icon: Icon(Icons.tab), label: 'Section C'), ], currentIndex: navigationShell.currentIndex, onTap: (int index) => _onTap(context, index), ), ); } void _onTap(BuildContext context, int index) { navigationShell.goBranch( index, initialLocation: index == navigationShell.currentIndex, ); } } class RootScreen extends StatelessWidget { const RootScreen({ required this.label, super.key, }); final String label; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Root of section $label')), body: Center(child: Text('Screen $label')), ); } }
关键修改说明
- 移除冗余的CustomTransitionPage:Tab切换不属于路由页面跳转,因此GoRoute和StatefulShellRoute无需使用pageBuilder,改回普通的builder即可。
- 添加AnimatedSwitcher:通过
KeyedSubtree给每个Tab对应的shell设置唯一key,确保切换时AnimatedSwitcher能检测到组件变化并触发动画。 - 自定义过渡效果:在AnimatedSwitcher的
transitionBuilder中可以实现任意过渡动画,比如淡入淡出、滑动、缩放等,替换示例中的FadeTransition即可。
如果需要更复杂的路由级过渡,你需要自定义StatefulShellRoute的shell实现,放弃indexedStack,手动管理分支Navigator的切换逻辑,但这种场景相对少见,上述方案已能满足绝大多数Tab切换动画需求。
内容的提问来源于stack exchange,提问作者Ahmed Ali
相关产品推荐
相关产品推荐

