第5章:路由与导航
在移动应用开发中,页面间的跳转是最基本也是最重要的功能之一。就像我们在现实生活中需要从一个房间走到另一个房间一样,在App中,用户需要在不同的界面间自由切换。Flutter提供了强大而灵活的路由系统来管理这些页面跳转,本章将深入探讨Flutter的路由与导航机制。
5.1 Navigator 1.0基础路由管理
5.1.1 什么是路由?
在Flutter中,**路由(Route)可以理解为应用中的一个页面或屏幕。每个路由都是一个Widget,通常是一个完整的界面。而导航(Navigation)**就是在这些路由之间进行切换的过程。
想象一下,如果把Flutter应用比作一栋楼房,那么每个路由就是楼房中的一个房间,而Navigator就像是连接这些房间的走廊和楼梯,帮助我们在不同房间间移动。
5.1.2 Navigator基础概念
Navigator是Flutter中管理路由栈的核心组件。它维护着一个路由栈(Route Stack),类似于我们常说的"后进先出"的数据结构。
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('首页'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// 最基础的页面跳转
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(),
),
);
},
child: Text('跳转到详情页'),
),
),
);
}
}
class DetailPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('详情页'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// 返回上一页
Navigator.pop(context);
},
child: Text('返回'),
),
),
);
}
}
5.1.3 常用的导航方法
Navigator提供了多种导航方法,每种都有其特定的使用场景:
class NavigationDemo extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('导航方法演示')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 1. push - 跳转到新页面
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NewPage()),
);
},
child: Text('Push - 跳转'),
),
// 2. pushReplacement - 替换当前页面
ElevatedButton(
onPressed: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => ReplacementPage()),
);
},
child: Text('Push Replacement - 替换'),
),
// 3. pushAndRemoveUntil - 跳转并清空特定路由
ElevatedButton(
onPressed: () {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => HomePage()),
(route) => false, // 清空所有路由
);
},
child: Text('Push And Remove Until - 清空并跳转'),
),
// 4. pop - 返回上一页
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Pop - 返回'),
),
],
),
);
}
}
5.1.4 路由动画自定义
Flutter允许我们自定义页面切换的动画效果,让应用体验更加流畅:
class CustomRouteAnimation extends PageRouteBuilder {
final Widget child;
CustomRouteAnimation({
required this.child})
: super(
transitionDuration: Duration(milliseconds: 500),
pageBuilder: (context, animation, secondaryAnimation) => child,
);
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
// 滑入动画
return SlideTransition(
position: Tween<Offset>(
begin: Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
}
// 使用自定义动画
void navigateWithCustomAnimation(BuildContext context) {
Navigator.push(
context,
CustomRouteAnimation(child: DetailPage()),
);
}
5.2 命名路由与路由表配置
5.2.1 为什么需要命名路由?
随着应用规模的增长,我们会发现直接使用MaterialPageRoute
会让代码变得难以维护。命名路由就像给每个页面起一个独特的名字,让我们可以通过名字来进行导航,这样代码更清晰、更易维护。
5.2.2 配置路由表
在应用的根部配置路由表是管理大型应用路由的最佳实践:
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter路由演示',
// 设置初始路由
initialRoute: '/',
// 配置路由表
routes: {
'/': (context) => HomePage(),
'/detail': (context) => DetailPage(),
'/profile': (context) => ProfilePage(),
'/settings': (context) => SettingsPage(),
'/login': (context) => LoginPage(),
},
// 处理未定义的路由
onUnknownRoute: (settings) {
return MaterialPageRoute(
builder: (context) => NotFoundPage(),
);
},
);
}
}
5.2.3 使用命名路由进行导航
有了路由表,页面跳转就变得简单明了:
class NavigationWithNamedRoutes extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('命名路由导航')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
// 使用命名路由跳转
Navigator.pushNamed(context, '/detail');
},
child: Text('跳转到详情页'),
),
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/profile');
},
child: Text('跳转到个人资料'),
),
ElevatedButton(
onPressed: () {
// 替换当前页面
Navigator.pushReplacementNamed(context, '/login');
},
child: Text('跳转到登录页(替换)'),
),
],
),
),
);
}
}
5.2.4 动态路由生成
对于需要动态生成的路由,我们可以使用onGenerateRoute
:
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: (settings) {
// 根据路由名称动态生成页面
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (context) => HomePage());
case '/detail':
// 可以从settings.arguments获取传递的参数
final args = settings.arguments as Map<String, dynamic>?;
return MaterialPageRoute(
builder: (context) => DetailPage(data: args?['data']),
);
case '/user':
// 支持路径参数,如 /user/123
final userId = settings.name!.split('/').last;
return MaterialPageRoute(
builder: (context) => UserPage(userId: userId),
);
default:
return MaterialPageRoute(builder: (context) => NotFoundPage());
}
},
);
}
}
5.3 页面间数据传递的多种方式
5.3.1 通过构造函数传递数据
这是最直接的数据传递方式,适用于简单的数据传递:
class ProductListPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('商品列表')),
body: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ListTile(
title: Text(product.name),
subtitle: Text('¥${product.price}'),
onTap: () {
// 通过构造函数传递商品数据
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailPage(product: product),
),
);
},
);
},
),
);
}
}
class ProductDetailPage extends StatelessWidget {
final Product product;
// 通过构造函数接收数据
const ProductDetailPage({
Key? key, required this.product}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(product.name)),
body: Column(
children: [
Image.network(product.imageUrl),
Text('价格: ¥${product.price}'),
Text(product.description),
],
),
);
}
}
5.3.2 通过命名路由传递参数
使用命名路由时,我们可以通过arguments
参数传递数据:
// 发送数据的页面
class DataSenderPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
onPressed: () {
// 通过arguments传递数据
Navigator.pushNamed(
context,
'/receiver',
arguments: {
'title': '传递的标题',
'message': '这是传递的消息',
'count': 42,
},
);
},
child: Text('发送数据'),
),
);
}
}
// 接收数据的页面
class DataReceiverPage extends StatelessWidget {
Widget build(BuildContext context) {
// 获取传递的参数
final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
return Scaffold(
appBar: AppBar(title: Text(args['title'])),
body: Column(
children: [
Text('消息: ${args['message']}'),
Text('数量: ${args['count']}'),
],
),
);
}
}
5.3.3 返回数据给上一页
有时我们需要从子页面返回数据给父页面,比如从设置页面返回用户选择的配置:
class SettingsPage extends StatefulWidget {
_SettingsPageState createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
bool _notificationsEnabled = true;
String _selectedTheme = 'light';
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('设置'),
actions: [
TextButton(
onPressed: () {
// 返回设置数据给上一页
Navigator.pop(context, {
'notifications': _notificationsEnabled,
'theme': _selectedTheme,
});
},
child: Text('保存'),
),
],
),
body: Column(
children: [
SwitchListTile(
title: Text('推送通知'),
value: _notificationsEnabled,
onChanged: (value) {
setState(() {
_notificationsEnabled = value;
});
},
),
ListTile(
title: Text('主题'),
subtitle: Text(_selectedTheme),
onTap