Flutter Navigator 深度学习教程
目录
- [Navigator 基础概念](#Navigator 基础概念 "#1-navigator-%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5")
- 核心类和接口
- [路由管理 API](#路由管理 API "#3-%E8%B7%AF%E7%94%B1%E7%AE%A1%E7%90%86-api")
- [声明式导航 (Pages API)](#声明式导航 (Pages API) "#4-%E5%A3%B0%E6%98%8E%E5%BC%8F%E5%AF%BC%E8%88%AA-pages-api")
- 状态恢复
- 高级特性
- 最佳实践
- 实战示例
1. Navigator 基础概念
1.1 什么是 Navigator?
Navigator 是 Flutter 中管理路由栈的核心 Widget,它维护一个基于栈的路由历史记录,支持路由的推入(push)和弹出(pop)操作。
dart
// Navigator 的基本结构
Navigator(
pages: <Page<dynamic>>[], // 声明式页面列表
onPopPage: (route, result) => true, // 页面弹出回调
initialRoute: '/', // 初始路由
onGenerateRoute: (settings) {}, // 路由生成器
observers: [], // 路由观察者
)
1.2 核心概念
Route(路由)
- 路由是对屏幕或页面的抽象
- 包含视觉呈现和过渡动画
- 可以返回结果值
RouteSettings(路由设置)
dart
class RouteSettings {
const RouteSettings({
this.name, // 路由名称,如 "/settings"
this.arguments, // 传递给路由的参数
});
final String? name;
final Object? arguments;
}
Page(页面)
- Page 是 RouteSettings 的子类
- 用于声明式导航
- 支持状态恢复
2. 核心类和接口
2.1 Route 类
dart
abstract class Route<T> {
// 路由所属的 Navigator
NavigatorState? get navigator;
// 路由的设置信息
RouteSettings get settings;
// Overlay 条目列表
List<OverlayEntry> get overlayEntries;
// 生命周期方法
void install() {}
TickerFuture didPush() {}
void didAdd() {}
bool didPop(T? result) {}
void didComplete(T? result) {}
// 状态查询
bool get isCurrent; // 是否是栈顶路由
bool get isFirst; // 是否是栈底路由
bool get isActive; // 是否在栈中
}
2.2 NavigatorState 类
NavigatorState 是 Navigator 的状态类,提供了所有导航操作的方法:
dart
class NavigatorState extends State<Navigator> {
// 命令式 API - push 系列
Future<T?> push<T>(Route<T> route);
Future<T?> pushNamed<T>(String routeName, {Object? arguments});
Future<T?> pushReplacement<T, TO>(Route<T> newRoute, {TO? result});
Future<T?> pushAndRemoveUntil<T>(Route<T> newRoute, RoutePredicate predicate);
// 命令式 API - pop 系列
void pop<T>([T? result]);
void popUntil(RoutePredicate predicate);
Future<bool> maybePop<T>([T? result]);
bool canPop();
// 可恢复 API
String restorablePush<T>(RestorableRouteBuilder<T> routeBuilder, {Object? arguments});
String restorablePushNamed<T>(String routeName, {Object? arguments});
// 路由替换
void replace<T>({required Route<dynamic> oldRoute, required Route<T> newRoute});
void replaceRouteBelow<T>({required Route<dynamic> anchorRoute, required Route<T> newRoute});
// 路由移除
void removeRoute(Route<dynamic> route);
void removeRouteBelow(Route<dynamic> anchorRoute);
}
2.3 NavigatorObserver(导航观察者)
dart
class NavigatorObserver {
NavigatorState? get navigator;
// 观察路由变化
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {}
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {}
void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) {}
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {}
// 观察用户手势
void didStartUserGesture(Route<dynamic> route, Route<dynamic>? previousRoute) {}
void didStopUserGesture() {}
}
3. 路由管理 API
3.1 Push 操作
基础 Push
dart
// 1. 使用 Route 对象
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) => const MyPage(),
),
);
// 2. 使用命名路由
Navigator.pushNamed(
context,
'/settings',
arguments: {'userId': 123},
);
// 3. 可恢复 Push(支持状态恢复)
@pragma('vm:entry-point')
static Route<void> _myRouteBuilder(BuildContext context, Object? arguments) {
return MaterialPageRoute<void>(
builder: (context) => MyPage(data: arguments),
);
}
Navigator.restorablePush(context, _myRouteBuilder, arguments: myData);
PushReplacement(替换当前路由)
dart
// 替换当前路由,常用于登录后跳转
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomePage(),
),
);
// 命名路由版本
Navigator.pushReplacementNamed(context, '/home', result: 'login_success');
PushAndRemoveUntil(移除直到某条件)
dart
// 推入新路由并移除所有之前的路由
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => const HomePage()),
(Route<dynamic> route) => false, // 移除所有路由
);
// 推入新路由并保留到某个路由
Navigator.pushNamedAndRemoveUntil(
context,
'/home',
ModalRoute.withName('/login'), // 保留到 /login 路由
);
3.2 Pop 操作
基础 Pop
dart
// 1. 简单返回
Navigator.pop(context);
// 2. 返回数据
Navigator.pop(context, 'result_data');
// 3. 安全返回(检查是否可以返回)
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
// 4. 尝试返回(如果不能返回则不执行)
final bool didPop = await Navigator.maybePop(context);
if (didPop) {
print('已返回');
} else {
print('无法返回,可能是根路由');
}
PopUntil(返回到某个条件)
dart
// 返回到首页
Navigator.popUntil(context, ModalRoute.withName('/'));
// 返回到满足条件的路由
Navigator.popUntil(context, (route) {
return route.settings.name == '/target' || route.isFirst;
});
3.3 获取 Navigator
dart
// 1. 获取最近的 Navigator
final navigator = Navigator.of(context);
// 2. 获取根 Navigator(跳过嵌套的 Navigator)
final rootNavigator = Navigator.of(context, rootNavigator: true);
// 3. 安全获取 Navigator(可能返回 null)
final navigator = Navigator.maybeOf(context);
if (navigator != null) {
navigator.pop();
}
4. 声明式导航 (Pages API)
4.1 基本概念
声明式导航使用 pages
属性来管理路由栈,更符合 Flutter 的声明式编程范式。
dart
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final List<Page> _pages = [];
@override
void initState() {
super.initState();
_pages.add(MaterialPage(
key: const ValueKey('HomePage'),
child: HomePage(
onNavigate: _handleNavigate,
),
));
}
void _handleNavigate(String destination) {
setState(() {
_pages.add(MaterialPage(
key: ValueKey(destination),
child: DetailPage(destination: destination),
));
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Navigator(
pages: List.of(_pages),
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
setState(() {
_pages.remove(route.settings);
});
return true;
},
),
);
}
}
4.2 Page 类
dart
// 自定义 Page
class MyCustomPage extends Page {
const MyCustomPage({
required this.child,
super.key,
super.name,
super.arguments,
super.restorationId,
});
final Widget child;
@override
Route createRoute(BuildContext context) {
return MaterialPageRoute(
settings: this,
builder: (context) => child,
);
}
}
4.3 TransitionDelegate(过渡委托)
自定义路由过渡行为:
dart
class NoAnimationTransitionDelegate extends TransitionDelegate<void> {
@override
Iterable<RouteTransitionRecord> resolve({
required List<RouteTransitionRecord> newPageRouteHistory,
required Map<RouteTransitionRecord?, RouteTransitionRecord> locationToExitingPageRoute,
required Map<RouteTransitionRecord?, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
}) {
final List<RouteTransitionRecord> results = [];
// 所有进入的路由不使用动画
for (final pageRoute in newPageRouteHistory) {
if (pageRoute.isWaitingForEnteringDecision) {
pageRoute.markForAdd(); // 无动画添加
}
results.add(pageRoute);
}
// 所有退出的路由不使用动画
for (final exitingPageRoute in locationToExitingPageRoute.values) {
if (exitingPageRoute.isWaitingForExitingDecision) {
exitingPageRoute.markForRemove(); // 无动画移除
}
results.add(exitingPageRoute);
}
return results;
}
}
// 使用自定义 TransitionDelegate
Navigator(
pages: pages,
onPopPage: onPopPage,
transitionDelegate: NoAnimationTransitionDelegate(),
)
5. 状态恢复
5.1 启用状态恢复
dart
// 1. 为 Navigator 提供 restorationScopeId
Navigator(
restorationScopeId: 'main_navigator',
// ...
)
// 2. 为 Page 提供 restorationId
MaterialPage(
key: ValueKey('detail_page'),
restorationId: 'detail_page_1',
child: DetailPage(),
)
// 3. 使用可恢复的命令式 API
final String routeId = Navigator.restorablePushNamed(
context,
'/settings',
arguments: {'section': 'privacy'},
);
5.2 RestorableRouteFuture
用于跟踪可恢复路由的返回值:
dart
class _MyPageState extends State<MyPage> with RestorationMixin {
final RestorableRouteFuture<int> _counterRouteFuture =
RestorableRouteFuture<int>(
onPresent: (NavigatorState navigator, Object? arguments) {
return navigator.restorablePush(
_buildCounterRoute,
arguments: arguments,
);
},
onComplete: (int result) {
setState(() {
_counter = result;
});
},
);
@override
String get restorationId => 'my_page';
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
registerForRestoration(_counterRouteFuture, 'counter_route');
}
void _showCounter() {
_counterRouteFuture.present(_counter);
}
@pragma('vm:entry-point')
static Route<int> _buildCounterRoute(
BuildContext context,
Object? arguments,
) {
return MaterialPageRoute<int>(
builder: (context) => CounterPage(
initialValue: arguments as int,
),
);
}
}
6. 高级特性
6.1 嵌套 Navigator
dart
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Navigator(
// 嵌套的 Navigator
onGenerateRoute: (settings) {
return MaterialPageRoute(
builder: (context) {
switch (settings.name) {
case '/tab1':
return Tab1Page();
case '/tab2':
return Tab2Page();
default:
return Tab1Page();
}
},
);
},
),
bottomNavigationBar: BottomNavigationBar(
onTap: (index) {
// 使用嵌套 Navigator
Navigator.of(context).pushReplacementNamed(
index == 0 ? '/tab1' : '/tab2',
);
},
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Tab 1'),
BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Tab 2'),
],
),
);
}
}
6.2 HeroController
Hero 动画控制:
dart
// 提供 HeroController
HeroControllerScope(
controller: HeroController(),
child: Navigator(
// ...
),
)
// 使用 Hero 动画
Hero(
tag: 'profile_image',
child: Image.network(imageUrl),
)
6.3 RoutePopDisposition
控制路由弹出行为:
dart
class MyRoute<T> extends PageRoute<T> {
@override
RoutePopDisposition get popDisposition {
if (hasUnsavedChanges) {
return RoutePopDisposition.doNotPop; // 阻止返回
}
if (isFirstRoute) {
return RoutePopDisposition.bubble; // 传递给上层
}
return RoutePopDisposition.pop; // 正常返回
}
@override
void onPopInvoked(bool didPop) {
if (!didPop && hasUnsavedChanges) {
// 显示确认对话框
showUnsavedChangesDialog();
}
}
}
6.4 用户手势追踪
dart
class MyNavigatorObserver extends NavigatorObserver {
@override
void didStartUserGesture(
Route<dynamic> route,
Route<dynamic>? previousRoute,
) {
print('用户开始滑动返回手势');
// 可以暂停动画、禁用某些功能等
}
@override
void didStopUserGesture() {
print('用户手势结束');
// 恢复功能
}
}
7. 最佳实践
7.1 路由命名规范
dart
class AppRoutes {
// 使用常量定义路由名称
static const String home = '/';
static const String login = '/login';
static const String profile = '/profile';
static const String settings = '/settings';
static const String settingsPrivacy = '/settings/privacy';
// 使用工厂方法生成带参数的路由
static String userProfile(String userId) => '/user/$userId';
}
// 使用
Navigator.pushNamed(context, AppRoutes.profile);
Navigator.pushNamed(context, AppRoutes.userProfile('123'));
7.2 参数传递
dart
// 1. 使用 arguments 参数
Navigator.pushNamed(
context,
'/detail',
arguments: DetailPageArguments(
id: '123',
title: 'My Title',
),
);
// 在目标页面接收
class DetailPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as DetailPageArguments;
return Scaffold(
appBar: AppBar(title: Text(args.title)),
// ...
);
}
}
// 2. 使用构造函数(推荐)
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(
id: '123',
title: 'My Title',
),
),
);
7.3 错误处理
dart
MaterialApp(
onGenerateRoute: (settings) {
// 处理已知路由
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) => HomePage());
case '/profile':
return MaterialPageRoute(builder: (_) => ProfilePage());
}
return null;
},
onUnknownRoute: (settings) {
// 处理未知路由
return MaterialPageRoute(
builder: (_) => NotFoundPage(routeName: settings.name),
);
},
)
7.4 等待返回结果
dart
// 推入页面并等待结果
final result = await Navigator.push<String>(
context,
MaterialPageRoute(
builder: (context) => SelectionPage(),
),
);
if (result != null) {
print('用户选择了: $result');
} else {
print('用户取消了选择');
}
// 在 SelectionPage 中返回结果
Navigator.pop(context, 'selected_value');
7.5 条件导航
dart
void navigateToNextPage() {
if (!isLoggedIn) {
Navigator.pushNamed(context, '/login');
return;
}
if (!hasCompletedProfile) {
Navigator.pushNamed(context, '/complete_profile');
return;
}
Navigator.pushNamed(context, '/dashboard');
}
8. 实战示例
8.1 完整的应用导航结构
dart
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Navigator Demo',
initialRoute: '/',
routes: {
'/': (context) => const HomePage(),
'/login': (context) => const LoginPage(),
'/profile': (context) => const ProfilePage(),
},
onGenerateRoute: (settings) {
// 处理动态路由
if (settings.name?.startsWith('/user/') ?? false) {
final userId = settings.name!.split('/').last;
return MaterialPageRoute(
builder: (context) => UserPage(userId: userId),
);
}
return null;
},
onUnknownRoute: (settings) {
return MaterialPageRoute(
builder: (context) => NotFoundPage(),
);
},
navigatorObservers: [
MyNavigatorObserver(),
],
);
}
}
8.2 带动画的自定义路由
dart
class SlideRightRoute extends PageRouteBuilder {
final Widget page;
SlideRightRoute({required this.page})
: super(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(1.0, 0.0);
const end = Offset.zero;
const curve = Curves.easeInOut;
final tween = Tween(begin: begin, end: end)
.chain(CurveTween(curve: curve));
final offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
},
transitionDuration: const Duration(milliseconds: 300),
);
}
// 使用
Navigator.push(
context,
SlideRightRoute(page: DetailPage()),
);
8.3 底部弹窗导航
dart
void showBottomSheetRoute(BuildContext context) {
Navigator.push(
context,
ModalBottomSheetRoute(
builder: (context) => Container(
height: 400,
child: Column(
children: [
ListTile(
title: Text('选项 1'),
onTap: () => Navigator.pop(context, '选项 1'),
),
ListTile(
title: Text('选项 2'),
onTap: () => Navigator.pop(context, '选项 2'),
),
],
),
),
),
);
}
8.4 对话框作为路由
dart
Future<bool?> showConfirmDialog(BuildContext context) {
return Navigator.push<bool>(
context,
DialogRoute(
context: context,
builder: (context) => AlertDialog(
title: Text('确认'),
content: Text('确定要删除吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text('确定'),
),
],
),
),
);
}
// 使用
final confirmed = await showConfirmDialog(context);
if (confirmed == true) {
deleteItem();
}
8.5 带状态恢复的完整示例
dart
class MyHomePage extends StatefulWidget {
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with RestorationMixin {
final RestorableInt _counter = RestorableInt(0);
late RestorableRouteFuture<int> _detailRouteFuture;
@override
void initState() {
super.initState();
_detailRouteFuture = RestorableRouteFuture<int>(
onPresent: (navigator, arguments) {
return navigator.restorablePush(
_buildDetailRoute,
arguments: arguments,
);
},
onComplete: (result) {
setState(() {
_counter.value = result;
});
},
);
}
@override
String get restorationId => 'home_page';
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
registerForRestoration(_counter, 'counter');
registerForRestoration(_detailRouteFuture, 'detail_route');
}
@override
void dispose() {
_counter.dispose();
_detailRouteFuture.dispose();
super.dispose();
}
@pragma('vm:entry-point')
static Route<int> _buildDetailRoute(
BuildContext context,
Object? arguments,
) {
return MaterialPageRoute<int>(
builder: (context) => DetailPage(
initialValue: arguments as int,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Counter: ${_counter.value}'),
ElevatedButton(
onPressed: () => _detailRouteFuture.present(_counter.value),
child: Text('Go to Detail'),
),
],
),
),
);
}
}
总结
Navigator 是 Flutter 导航系统的核心,掌握以下要点:
- 命令式 API :适合简单场景,使用
push
、pop
等方法 - 声明式 API :适合复杂应用,使用
pages
属性管理路由栈 - 状态恢复 :使用
restorablePush
系列方法和RestorableRouteFuture
- 路由观察 :使用
NavigatorObserver
监听路由变化 - 嵌套导航:合理使用多个 Navigator 构建复杂的导航结构
在 Mind App 项目中,建议:
- 使用命名路由管理主要页面
- 结合 GetX 路由管理简化代码
- 为重要流程启用状态恢复
- 使用 NavigatorObserver 进行路由埋点统计