第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