这是演示如何使用 BottomNavigationBar 设置嵌套导航的官方示例,其中每个栏项都使用自己的持久导航器,即为每个项单独维护导航状态。此设置还可以深度链接到嵌套页面。
现在我想在底部导航栏的页面之间添加CustomTransitionPage,所以我使用了pageBuilder而不是builder,但是没有成功。
这是添加 CustomTransitionPage 后的完整代码
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(
pageBuilder: (context, state, navigationShell) {
return CustomTransitionPage(
key: state.pageKey,
child: ScaffoldWithNavBar(navigationShell: navigationShell),
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
);
},
branches: <StatefulShellBranch>[
StatefulShellBranch(
navigatorKey: _sectionANavigatorKey,
routes: <RouteBase>[
GoRoute(
path: '/a',
pageBuilder: (context, state) => CustomTransitionPage(
key: state.pageKey,
child: const RootScreen(label: 'A'),
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
),
),
],
),
StatefulShellBranch(
routes: <RouteBase>[
GoRoute(
path: '/b',
pageBuilder: (context, state) => CustomTransitionPage(
key: state.pageKey,
child: const RootScreen(label: 'B'),
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
),
),
],
),
StatefulShellBranch(
routes: <RouteBase>[
GoRoute(
path: '/c',
pageBuilder: (context, state) => CustomTransitionPage(
key: state.pageKey,
child: const RootScreen(label: 'C'),
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
),
),
],
),
],
),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routerConfig: _router,
);
}
}
/// Builds the "shell" for the app by building a Scaffold with a
/// BottomNavigationBar, where [child] is placed in the body of the Scaffold.
class ScaffoldWithNavBar extends StatelessWidget {
const ScaffoldWithNavBar({
required this.navigationShell,
Key? key,
}) : super(key: key ?? const ValueKey<String>('ScaffoldWithNavBar'));
/// The navigation shell and container for the branch Navigators.
final StatefulNavigationShell navigationShell;
@override
Widget build(BuildContext context) {
return Scaffold(
body: 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,
);
}
}
/// Widget for the root/initial pages in the bottom navigation bar.
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'),
),
);
}
}
可以对过渡进行动画处理,但您必须自己制作动画/过渡,并且不会使用 CustomTransitionsPage。
您要做的是将一个函数传递给
StatefulShellRoute.navigatorContainerBuilder
,其中有 context
、StatefulNavigationShell
和 List<Children>
(它们是分支导航器)作为参数。
您想用它来构建
ScaffoldWithNavBar
,并传入 StatefulNavigationShell
和 List<Children.>
。然后你想在构建函数中做这样的事情:
@override
Widget build(BuildContext context) {
return Scaffold(
body: BranchAnimator(
currentIndex: navigationShell.currentIndex,
children: children,
),
...
BranchAnimator 可以像您想要的那样简单或复杂。最基本的(无动画)是一个简单的堆栈:
class BranchAnimator extends StatelessWidget {
const BranchAnimator({super.key, required this.currentIndex, required this.children});
final int currentIndex;
final List<Widget> children;
@override
Widget build(BuildContext context) {
return Stack(
children: children.mapIndexed(
(int i, Widget child) {
return Opacity( /// <<---- here
opacity: i == currentIndex ? 1 : 0,
child: IgnorePointer(
ignoring: i != currentIndex,
child: TickerMode(
enabled: i == currentIndex,
child: child,
),
),
);
}
),
);
}
}
这基本上复制了默认行为 - 如果你想实际执行动画,那么你需要修改
<<---- here
在上面代码中的位置 - 或者通过将该不透明度设置为 AnimatedOpacity 或更复杂的东西(如果你想要更多)自定义动画。您可以采用多种不同的方法来实现此目的,具体取决于您想要的动画类型以及您希望它的复杂程度。您的第一步可能是将 BranchAnimator
制作为有状态小部件,保留最后一个索引,以便您可以知道要绘制什么类型的动画,并且可能在 TickerProviderStateMixin 中混合,然后执行动画应该与您的方式非常相似会实现 CustomTransitionPage。
在您链接到的同一存储库中有一个示例,它对不透明度和大小进行基本动画:https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/others /custom_stateful_shell_route.dart.