Flutter开发实战之路由与导航
第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/123final 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