路由和导航可以说是一个项目中最重要的部分之一。越复杂的项目,路由管理就显得越重要。同样越复杂的业务场景,对路由的使用要求也越高。Flutter原生的路由管理就提供了非常丰富的功能,灵活使用可以帮助我们高效的完成不同的业务场景开发。
一、核心概念
- Navigator:管理页面(Route)的组件,通过维护一个 路由栈 实现页面跳转。
- Route:表示一个页面或视图,Flutter 提供了多种 Route 类型(如
MaterialPageRoute、CupertinoPageRoute)。 - 路由栈(Route Stack):Navigator 通过
push()、pop()等方法操作路由栈,实现页面导航。
二、基本用法
1. 直接导航(匿名路由)
通过 Navigator.push()(打开新页面,入栈) 和 Navigator.pop()(返回上一页,出栈) 实现页面跳转:
// 跳转到新页面(入栈)
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);
// 返回上一页(出栈)
Navigator.pop(context);
2. 命名路由
在 MaterialApp 中配置路由表,通过名称跳转:
return MaterialApp(
title: 'Flutter Demo',
initialRoute: "/",
routes: {
// '/': (context) => MyHomePage(), 注意:这里不能配置App的跟页面,因为和home 参数重复了。
'/textFieldDemo': (context) => TextFieldDemo(),
},
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
// 通过名称跳转
Navigator.pushNamed(context, '/textFieldDemo');
去掉home属性的写法
return MaterialApp(
title: 'Flutter Demo',
initialRoute: "/",
routes: {
'/': (context) => MyHomePage(title: 'title'),//因为MyHomePage是这个页面的根页面。
'/textFieldDemo': (context) => TextFieldDemo(),
},
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
)
);
// 通过名称跳转
Navigator.pushNamed(context, '/textFieldDemo');
3. 路由传参
- 接受
title参数的页面
class SecondPage extends StatelessWidget {
final String? title;
const SecondPage({super.key, @required this.title});
@override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments;
var id, name;
if (args != null && args is Map<String, dynamic>) {
id = args['ID'];
name = args['name'];
}
return Scaffold(
appBar: AppBar(title: Text(title ?? 'demo')),
body: Center(
child: Column(
children: [
Text('second page'),
if (id != null) Text('ID:$id') else Text('No ID'),
if (name != null) Text('name:$name') else Text('No name'),
],
),
),
);
}
}
-
直接传参(匿名路由):
Navigator.push( context, MaterialPageRoute( builder: (context) => SecondPage(title: '传递的标题'), ), ); -
命名路由传参:
// 跳转时传参 Navigator.pushNamed( context, '/second', arguments: {'ID': 'this is id', 'name': 'this is name'}, ),
4. 自定义路由过渡动画
通过 PageRouteBuilder 实现自定义动画:
Navigator.push(
context,
PageRouteBuilder(
transitionDuration: Duration(milliseconds: 500),
pageBuilder: (_, animation, secondaryAnimation) => SecondPage(title: 'Fade Transition'),
transitionsBuilder: (_, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: CurvedAnimation(parent: animation, curve: Curves.easeOut),
child: child,
);
},
),
);
5. 保持页面状态
使用 AutomaticKeepAliveClientMixin 保留页面状态(如滚动位置):
class KeepAlivePage extends StatefulWidget {
@override
_KeepAlivePageState createState() => _KeepAlivePageState();
}
class _KeepAlivePageState extends State<KeepAlivePage> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true; // 标记需要保留状态
@override
Widget build(BuildContext context) {
super.build(context); // 必须调用
return Scaffold(
appBar: AppBar(title: Text('Keep Alive Page')),
body: Center(child: Text('This page state is preserved')),
);
}
}
6. 路由监听与拦截
- 全局路由监听:
可以针对一些特殊的页面,比如权限配置,A/B测试等去做监听处理。
class MyNavigatorObserver extends NavigatorObserver {
@override
void didPush(Route route, Route? previousRoute) {
// 这里可以根据用户状态去判断进入不一样的页面。
if (route.settings.name == '/a') {
// 如果目标页面是/a,则判断用户是否是Admin,是则进入/a页面,否则重定向到/b
if(user.isAdmin()) {
print('进入A页面: ${route.settings.name}');
Future.delayed(Duration.zero, () {
Navigator.of(navigator!.context).pushReplacementNamed('/a');
});
} else {
print('进入B页面: ${route.settings.name}')
Future.delayed(Duration.zero, () {
Navigator.of(navigator!.context).pushReplacementNamed('/b');
});
}
}
}
}
@override
void didPush(Route route, Route? previousRoute) {
const allowedPages = user.getAllowedConfig();
if(!allowedPages.contains(route.settings.name)) { //如果需要跳转到页面不在列表,则返回
Navigator.of(navigator!.context).pop();
}
}
// 在 MaterialApp 中注册
MaterialApp(
navigatorObservers: [
MyNavigatorObserver(),
MyPermissionManagerObserver(),
]
);
- 拦截手机物理返回按钮:
WillPopScope( onWillPop: () async { if (shouldPreventBack) { showExitConfirmDialog(); // 显示确认对话框 return false; // 阻止返回 } return true; // 允许返回 }, child: Scaffold(...), );
进阶技巧
1. 替换当前页面
使用 pushReplacement() 替换当前页面(不保留在栈中):
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => NewPage()),
);
2. 返回到指定页面
使用 popUntil() 返回到某个特定页面:
Navigator.popUntil(context, ModalRoute.withName('/home'));
3. 动态路由生成
通过 onGenerateRoute 动态生成路由:
MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == '/detail') {
final args = settings.arguments as DetailArgs;
return MaterialPageRoute(
builder: (context) => DetailPage(args: args),
);
}
return null;
},
);
最佳实践
-
命名路由 vs 匿名路由:
- 小型应用:优先使用匿名路由,代码简洁。
- 中大型应用:使用命名路由,便于维护和统一管理。
-
路由栈管理:
- 避免路由栈过深(建议不超过 5 层)。
- 使用
Navigator.popUntil()清理不必要的路由。
-
性能优化:
- 对频繁切换的页面使用
AutomaticKeepAliveClientMixin。 - 监控路由栈内存占用,防止内存泄漏。
- 对频繁切换的页面使用
-
错误处理:
- 在
onGenerateRoute中处理未知路由:onGenerateRoute: (settings) { if (settings.name == '/404') { return MaterialPageRoute(builder: (context) => NotFoundPage()); } return null; },
- 在
完整示例代码
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomePage(),
'/second': (context) => SecondPage(),
},
onGenerateRoute: (settings) {
if (settings.name == '/detail') {
final args = settings.arguments as DetailArgs;
return MaterialPageRoute(
builder: (context) => DetailPage(args: args),
);
}
return null;
},
navigatorObservers: [MyNavigatorObserver()],
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('首页')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/second');
},
child: Text('跳转到第二页'),
),
),
);
}
}
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('第二页')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pushNamed(
context,
'/detail',
arguments: DetailArgs(id: 1, name: 'Detail Page'),
);
},
child: Text('跳转到详情页'),
),
),
);
}
}
class DetailPage extends StatelessWidget {
final DetailArgs args;
DetailPage({required this.args});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(args.name)),
body: Center(child: Text('ID: ${args.id}')),
);
}
}
@freezed
class DetailArgs with _$DetailArgs {
factory DetailArgs({required int id, required String name}) = _DetailArgs;
}
class MyNavigatorObserver extends NavigatorObserver {
@override
void didPush(Route route, Route? previousRoute) {
print('进入页面: ${route.settings.name}');
}
}
总结
- 基础用法:
push()/pop()实现简单导航,named routes管理复杂应用。 - 高级功能:自定义动画、嵌套导航、状态保持、路由监听。
- 最佳实践:合理规划路由栈,优化性能,处理异常场景。
Navigator的基础功能非常丰富,通过灵活运用 Navigator,可以降低复杂项目的路由管理成本,同时也可以让用户有一个更好的体验。