Flutter 进阶 - 路由导航(Navigation)
一、基础路由
1. Navigator.push(跳转)
定义:
将新页面推入路由栈。
基础用法:
// 跳转到新页面
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);
完整示例:
class FirstPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('首页')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);
},
child: Text('跳转到第二页'),
),
),
);
}
}
class SecondPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('第二页')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context); // 返回上一页
},
child: Text('返回'),
),
),
);
}
}
2. Navigator.pop(返回)
用法:
// 简单返回
Navigator.pop(context);
// 返回并传递数据
Navigator.pop(context, '返回的数据');
接收返回值:
class FirstPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () async {
// 等待返回值
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectPage()),
);
if (result != null) {
print('收到返回值:$result');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('选择了:$result')),
);
}
},
child: Text('选择选项'),
),
),
);
}
}
class SelectPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('选择')),
body: ListView(
children: [
ListTile(
title: Text('选项 A'),
onTap: () {
Navigator.pop(context, 'A'); // 返回并传值
},
),
ListTile(
title: Text('选项 B'),
onTap: () {
Navigator.pop(context, 'B');
},
),
],
),
);
}
}
3. 其他路由方法
// 替换当前路由
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => HomePage()),
);
// 清空路由栈并跳转
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => HomePage()),
(route) => false, // 移除所有路由
);
// 清空路由栈保留首页
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => HomePage()),
ModalRoute.withName('/'), // 保留首页
);
// 判断是否可以返回
if (Navigator.canPop(context)) {
Navigator.pop(context);
} else {
print('没有可返回的页面');
}
二、命名路由
4. 配置路由表
在 MaterialApp 中配置:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
// 初始路由
initialRoute: '/',
// 路由表
routes: {
'/': (context) => HomePage(),
'/second': (context) => SecondPage(),
'/third': (context) => ThirdPage(),
'/detail': (context) => DetailPage(),
'/profile': (context) => ProfilePage(),
'/settings': (context) => SettingsPage(),
},
);
}
}
5. 使用命名路由
基础跳转:
// 跳转
Navigator.pushNamed(context, '/second');
// 返回
Navigator.pop(context);
// 替换当前路由
Navigator.pushReplacementNamed(context, '/home');
// 清空路由栈并跳转
Navigator.pushNamedAndRemoveUntil(
context,
'/home',
(route) => false,
);
完整示例:
class HomePage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('首页')),
body: ListView(
children: [
ListTile(
leading: Icon(Icons.person),
title: Text('个人中心'),
trailing: Icon(Icons.arrow_forward_ios),
onTap: () {
Navigator.pushNamed(context, '/profile');
},
),
ListTile(
leading: Icon(Icons.settings),
title: Text('设置'),
trailing: Icon(Icons.arrow_forward_ios),
onTap: () {
Navigator.pushNamed(context, '/settings');
},
),
ListTile(
leading: Icon(Icons.info),
title: Text('详情'),
trailing: Icon(Icons.arrow_forward_ios),
onTap: () {
Navigator.pushNamed(context, '/detail');
},
),
],
),
);
}
}
三、路由传参
6. 构造函数传参
方式一:直接传参(基础路由):
// 跳转并传参
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(
id: 123,
title: '商品详情',
price: 99.99,
),
),
);
// 接收参数
class DetailPage extends StatelessWidget {
final int id;
final String title;
final double price;
const DetailPage({
required this.id,
required this.title,
required this.price,
});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('ID: $id', style: TextStyle(fontSize: 18)),
SizedBox(height: 10),
Text('标题: $title', style: TextStyle(fontSize: 18)),
SizedBox(height: 10),
Text('价格: ¥$price', style: TextStyle(fontSize: 24, color: Colors.red)),
],
),
),
);
}
}
7. 命名路由传参
使用 arguments:
// 跳转并传参
Navigator.pushNamed(
context,
'/detail',
arguments: {
'id': 123,
'title': '商品详情',
'price': 99.99,
},
);
// 接收参数
class DetailPage extends StatelessWidget {
Widget build(BuildContext context) {
// 获取传递的参数
final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
final id = args['id'];
final title = args['title'];
final price = args['price'];
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('ID: $id'),
Text('标题: $title'),
Text('价格: ¥$price'),
],
),
),
);
}
}
封装参数模型:
// 定义参数模型
class ProductDetailArgs {
final int id;
final String title;
final double price;
ProductDetailArgs({
required this.id,
required this.title,
required this.price,
});
}
// 跳转并传参
Navigator.pushNamed(
context,
'/detail',
arguments: ProductDetailArgs(
id: 123,
title: '商品详情',
price: 99.99,
),
);
// 接收参数
class DetailPage extends StatelessWidget {
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as ProductDetailArgs;
return Scaffold(
appBar: AppBar(title: Text(args.title)),
body: Column(
children: [
Text('ID: ${args.id}'),
Text('价格: ¥${args.price}'),
],
),
);
}
}
四、路由拦截
8. onGenerateRoute
定义:
动态生成路由,可以实现路由拦截、权限验证等功能。
基础用法:
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: (settings) {
// 打印路由信息
print('路由名称: ${settings.name}');
print('路由参数: ${settings.arguments}');
// 根据路由名称返回对应页面
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (context) => HomePage());
case '/second':
return MaterialPageRoute(builder: (context) => SecondPage());
case '/detail':
return MaterialPageRoute(builder: (context) => DetailPage());
default:
return MaterialPageRoute(builder: (context) => NotFoundPage());
}
},
);
}
}
9. 路由守卫(登录验证)
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: (settings) {
// 检查登录状态
bool isLoggedIn = checkLoginStatus();
// 需要登录的路由列表
List<String> protectedRoutes = ['/profile', '/cart', '/orders'];
// 如果访问受保护的路由且未登录,跳转到登录页
if (protectedRoutes.contains(settings.name) && !isLoggedIn) {
return MaterialPageRoute(
builder: (context) => LoginPage(
redirectTo: settings.name, // 登录后跳转的页面
),
);
}
// 路由分发
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (context) => HomePage());
case '/profile':
return MaterialPageRoute(builder: (context) => ProfilePage());
case '/cart':
return MaterialPageRoute(builder: (context) => CartPage());
case '/orders':
return MaterialPageRoute(builder: (context) => OrdersPage());
case '/login':
return MaterialPageRoute(builder: (context) => LoginPage());
default:
return MaterialPageRoute(builder: (context) => NotFoundPage());
}
},
);
}
bool checkLoginStatus() {
// 检查登录状态的逻辑
// 可以从 SharedPreferences 或其他状态管理中获取
return false;
}
}
// 登录页
class LoginPage extends StatelessWidget {
final String? redirectTo;
const LoginPage({this.redirectTo});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('登录')),
body: Center(
child: ElevatedButton(
onPressed: () {
// 登录成功后
if (redirectTo != null) {
// 跳转到原来要访问的页面
Navigator.pushReplacementNamed(context, redirectTo!);
} else {
// 跳转到首页
Navigator.pushReplacementNamed(context, '/');
}
},
child: Text('登录'),
),
),
);
}
}
10. 路由守卫封装
class RouteGuard {
// 路由生成器
static Route<dynamic>? generateRoute(RouteSettings settings) {
// 获取登录状态
bool isLoggedIn = _checkLoginStatus();
// 需要登录的路由
List<String> protectedRoutes = ['/profile', '/cart', '/orders', '/favorites'];
// 需要管理员权限的路由
List<String> adminRoutes = ['/admin', '/users', '/settings'];
// 登录验证
if (protectedRoutes.contains(settings.name) && !isLoggedIn) {
return MaterialPageRoute(
builder: (context) => LoginPage(redirectTo: settings.name),
);
}
// 管理员权限验证
if (adminRoutes.contains(settings.name)) {
if (!isLoggedIn) {
return MaterialPageRoute(
builder: (context) => LoginPage(redirectTo: settings.name),
);
}
if (!_isAdmin()) {
return MaterialPageRoute(
builder: (context) => ForbiddenPage(),
);
}
}
// 路由分发
return _getRoute(settings);
}
// 检查登录状态
static bool _checkLoginStatus() {
// 从存储中获取登录状态
return false;
}
// 检查管理员权限
static bool _isAdmin() {
// 从存储中获取用户角色
return false;
}
// 获取路由
static Route<dynamic>? _getRoute(RouteSettings settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (context) => HomePage());
case '/profile':
return MaterialPageRoute(builder: (context) => ProfilePage());
case '/cart':
return MaterialPageRoute(builder: (context) => CartPage());
case '/orders':
return MaterialPageRoute(builder: (context) => OrdersPage());
case '/admin':
return MaterialPageRoute(builder: (context) => AdminPage());
case '/login':
return MaterialPageRoute(builder: (context) => LoginPage());
default:
return MaterialPageRoute(builder: (context) => NotFoundPage());
}
}
}
// 在 MaterialApp 中使用
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: RouteGuard.generateRoute,
);
}
}
五、页面转场动画
11. 自定义转场动画
// 淡入淡出动画
class FadeRoute extends PageRouteBuilder {
final Widget page;
FadeRoute({required this.page})
: super(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
);
}
// 使用
Navigator.push(
context,
FadeRoute(page: SecondPage()),
);
// 缩放动画
class ScaleRoute extends PageRouteBuilder {
final Widget page;
ScaleRoute({required this.page})
: super(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return ScaleTransition(
scale: Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: animation, curve: Curves.easeInOut),
),
child: child,
);
},
);
}
// 滑动动画
class SlideRoute extends PageRouteBuilder {
final Widget page;
SlideRoute({required this.page})
: super(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
var begin = Offset(1.0, 0.0);
var end = Offset.zero;
var curve = Curves.ease;
var tween = Tween(begin: begin, end: end).chain(
CurveTween(curve: curve),
);
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
);
}
五、返回拦截
1. PopScope(Flutter 3.12+)
定义:
拦截返回操作,防止用户误触返回按钮。
基础用法:
class EditPage extends StatefulWidget {
_EditPageState createState() => _EditPageState();
}
class _EditPageState extends State<EditPage> {
bool _hasUnsavedChanges = false;
Widget build(BuildContext context) {
return PopScope(
canPop: !_hasUnsavedChanges, // false时拦截返回
onPopInvoked: (bool didPop) {
if (didPop) {
return;
}
// 显示确认对话框
_showExitConfirmDialog();
},
child: Scaffold(
appBar: AppBar(title: Text('编辑页面')),
body: Column(
children: [
TextField(
onChanged: (value) {
setState(() {
_hasUnsavedChanges = value.isNotEmpty;
});
},
decoration: InputDecoration(hintText: '输入内容'),
),
Text(
_hasUnsavedChanges ? '有未保存的更改' : '无更改',
style: TextStyle(color: Colors.red),
),
],
),
),
);
}
void _showExitConfirmDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('退出确认'),
content: Text('您有未保存的更改,确定要退出吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(context); // 关闭对话框
Navigator.pop(context); // 退出页面
},
child: Text('确定'),
),
],
),
);
}
}
2. WillPopScope(旧版本兼容)
定义:
Flutter 3.12 之前使用的返回拦截组件。
基础用法:
WillPopScope(
onWillPop: () async {
// 返回 true 允许返回,false 拦截返回
return await _showExitDialog(context) ?? false;
},
child: Scaffold(
appBar: AppBar(title: Text('页面')),
body: Center(child: Text('内容')),
),
)
Future<bool?> _showExitDialog(BuildContext context) {
return showDialog<bool>(
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('确定'),
),
],
),
);
}
3. 双击退出应用
定义:
在首页双击返回键退出应用,防止误触。
完整示例:
class HomePage extends StatefulWidget {
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
DateTime? _lastPressedTime;
Widget build(BuildContext context) {
return PopScope(
canPop: false,
onPopInvoked: (bool didPop) {
if (didPop) {
return;
}
_exitApp();
},
child: Scaffold(
appBar: AppBar(title: Text('首页')),
body: Center(child: Text('双击返回键退出应用')),
),
);
}
void _exitApp() {
if (_lastPressedTime == null ||
DateTime.now().difference(_lastPressedTime!) > Duration(seconds: 2)) {
// 两次点击间隔超过2秒,重新记录时间
_lastPressedTime = DateTime.now();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('再按一次退出应用'),
duration: Duration(seconds: 2),
),
);
} else {
// 退出应用
SystemNavigator.pop();
}
}
}
WillPopScope 版本(旧):
WillPopScope(
onWillPop: () async {
if (_lastPressedTime == null ||
DateTime.now().difference(_lastPressedTime!) > Duration(seconds: 2)) {
_lastPressedTime = DateTime.now();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('再按一次退出应用')),
);
return false;
}
return true;
},
child: Scaffold(...),
)
4. 表单保护示例
定义:
表单有未保存数据时拦截返回。
class FormPage extends StatefulWidget {
_FormPageState createState() => _FormPageState();
}
class _FormPageState extends State<FormPage> {
final _formKey = GlobalKey<FormState>();
bool _isModified = false;
String _name = '';
String _email = '';
Widget build(BuildContext context) {
return PopScope(
canPop: !_isModified,
onPopInvoked: (bool didPop) async {
if (didPop) {
return;
}
final shouldPop = await showDialog<bool>(
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('放弃更改'),
),
],
),
);
if (shouldPop == true && context.mounted) {
Navigator.pop(context);
}
},
child: Scaffold(
appBar: AppBar(
title: Text('表单编辑'),
actions: [
IconButton(
icon: Icon(Icons.save),
onPressed: _saveForm,
),
],
),
body: Form(
key: _formKey,
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: '姓名'),
onChanged: (value) {
setState(() {
_name = value;
_isModified = true;
});
},
),
TextFormField(
decoration: InputDecoration(labelText: '邮箱'),
onChanged: (value) {
setState(() {
_email = value;
_isModified = true;
});
},
),
],
),
),
),
),
);
}
void _saveForm() {
if (_formKey.currentState!.validate()) {
// 保存数据
print('保存:姓名=$_name, 邮箱=$_email');
setState(() {
_isModified = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('保存成功')),
);
}
}
}
5. 版本兼容处理
同时支持新旧版本:
import 'package:flutter/material.dart';
class CompatibleBackInterceptor extends StatelessWidget {
final Widget child;
final Future<bool> Function() onWillPop;
const CompatibleBackInterceptor({
required this.child,
required this.onWillPop,
});
Widget build(BuildContext context) {
// Flutter 3.12+ 使用 PopScope
// 旧版本使用 WillPopScope
return PopScope(
canPop: false,
onPopInvoked: (bool didPop) async {
if (didPop) {
return;
}
if (await onWillPop()) {
if (context.mounted) {
Navigator.pop(context);
}
}
},
child: child,
);
}
}
// 使用
CompatibleBackInterceptor(
onWillPop: () async {
return await _showExitDialog() ?? false;
},
child: Scaffold(...),
)
总结
路由方式对比
| 特性 | 基础路由 | 命名路由 |
|---|---|---|
| 使用方式 | Navigator.push | Navigator.pushNamed |
| 传参方式 | 构造函数 | arguments |
| 路由管理 | 分散 | 集中 |
| 代码维护 | 较困难 | 简单 |
| 适用场景 | 简单应用 | 中大型应用 |
最佳实践
- 中大型应用:使用命名路由 + onGenerateRoute
- 小型应用:使用基础路由即可
- 需要权限控制:使用路由守卫
- 传递复杂参数:封装参数模型
- 页面转场:自定义 PageRouteBuilder
180

被折叠的 条评论
为什么被折叠?



