Appearance
Flutter路由与导航 #
在Flutter中,导航是应用架构的重要组成部分,它允许用户在不同页面(在Flutter中称为"routes")之间进行切换。Flutter提供了多种导航方式,从简单的页面跳转到复杂的嵌套路由,本章将详细介绍Flutter中的导航机制。
基础导航 #
MaterialPageRoute #
MaterialPageRoute是Flutter中最常用的路由,它实现了Material Design的页面切换动画。
dart
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Navigation Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'这是主页',
style: Theme.of(context).textTheme.headlineSmall,
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);
},
child: Text('跳转到第二页'),
),
],
),
),
);
}
}
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'这是第二页',
style: Theme.of(context).textTheme.headlineSmall,
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('返回上一页'),
),
],
),
),
);
}
}
CupertinoPageRoute #
CupertinoPageRoute提供iOS风格的页面切换动画:
dart
class CupertinoNavigationDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Cupertino Navigation'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
CupertinoPageRoute(builder: (context) => CupertinoSecondPage()),
);
},
child: Text('iOS风格导航'),
),
),
);
}
}
class CupertinoSecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Cupertino Page'),
previousPageTitle: 'Back',
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('iOS风格页面'),
SizedBox(height: 20),
CupertinoButton(
child: Text('返回'),
onPressed: () {
Navigator.pop(context);
},
),
],
),
),
);
}
}
参数传递 #
在页面之间传递数据是常见的需求,Flutter提供了多种方式来实现:
传递简单参数 #
dart
class DataPassingDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('数据传递演示')),
body: Center(
child: ElevatedButton(
onPressed: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DataReceiverPage(
title: '来自首页的标题',
count: 42,
),
),
);
if (result != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('接收到返回数据: $result')),
);
}
},
child: Text('传递数据并等待返回'),
),
),
);
}
}
class DataReceiverPage extends StatelessWidget {
final String title;
final int count;
const DataReceiverPage({Key? key, required this.title, required this.count}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('接收到的数字: $count'),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pop(context, '返回的数据');
},
child: Text('返回并传递数据'),
),
],
),
),
);
}
}
传递复杂对象 #
dart
// 定义数据模型
class User {
final String name;
final int age;
final String email;
User({required this.name, required this.age, required this.email});
// 为了在路由间传递,需要实现toString方法或使用json序列化
@override
String toString() {
return 'User{name: $name, age: $age, email: $email}';
}
}
class ObjectPassingDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('对象传递演示')),
body: Center(
child: ElevatedButton(
onPressed: () {
final user = User(
name: '张三',
age: 25,
email: 'zhangsan@example.com',
);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UserProfilePage(user: user),
),
);
},
child: Text('传递用户对象'),
),
),
);
}
}
class UserProfilePage extends StatelessWidget {
final User user;
const UserProfilePage({Key? key, required this.user}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('用户资料')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('姓名: ${user.name}', style: TextStyle(fontSize: 18)),
SizedBox(height: 10),
Text('年龄: ${user.age}', style: TextStyle(fontSize: 18)),
SizedBox(height: 10),
Text('邮箱: ${user.email}', style: TextStyle(fontSize: 18)),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('返回'),
),
],
),
),
);
}
}
命名路由 #
命名路由提供了一种更结构化的方式来管理应用中的路由,特别适用于大型应用。
配置命名路由 #
dart
class NamedRouteApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Named Routes Demo',
initialRoute: '/', // 初始路由
routes: {
// 命名路由映射
'/': (context) => HomeNamedRoutePage(),
'/profile': (context) => ProfileNamedRoutePage(),
'/settings': (context) => SettingsNamedRoutePage(),
},
);
}
}
class HomeNamedRoutePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('首页')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/profile');
},
child: Text('跳转到资料页'),
),
SizedBox(height: 10),
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/settings');
},
child: Text('跳转到设置页'),
),
],
),
),
);
}
}
class ProfileNamedRoutePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('资料页')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('返回'),
),
),
);
}
}
class SettingsNamedRoutePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('设置页')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('返回'),
),
),
);
}
}
带参数的命名路由 #
dart
class NamedRouteWithArgsApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Named Routes with Arguments',
initialRoute: '/',
routes: {
'/': (context) => HomeWithArgsPage(),
'/detail': (context) {
// 从ModalRoute获取参数
final Map<String, dynamic> args = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>;
return DetailPage(
title: args['title'],
message: args['message'],
);
},
},
);
}
}
class HomeWithArgsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('首页')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pushNamed(
context,
'/detail',
arguments: {
'title': '详情页面',
'message': '这是从首页传递的消息',
},
);
},
child: Text('跳转到详情页'),
),
),
);
}
}
class DetailPage extends StatelessWidget {
final String title;
final String message;
const DetailPage({Key? key, required this.title, required this.message}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(message, style: TextStyle(fontSize: 18)),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('返回'),
),
],
),
),
),
);
}
}
导航器2.0(Router API) #
Flutter 2.0引入了新的Router API,提供了更灵活的路由管理方式。
dart
import 'package:flutter/material.dart';
// 定义应用的路由状态
class AppRoutePath extends RouteSettings {
final String location;
final String? id;
final String? action;
AppRoutePath.home() :
location = '/',
id = null,
action = null,
super(name: '/');
AppRoutePath.details(this.id) :
location = '/details/$id',
action = null,
super(name: '/details');
AppRoutePath.edit(this.id) :
location = '/details/$id/edit',
action = 'edit',
super(name: '/details');
bool get isHomePage => location == '/';
bool get isDetailsPage => id != null;
bool get isEditing => action == 'edit';
}
// 定义路由信息解析器
class AppRouterDelegate extends RouterDelegate<AppRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<AppRoutePath> {
AppRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> navigatorKey;
String? _selectedBook;
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: [
MaterialPage(
child: HomeRouterPage(
onBookTapped: _selectBook,
),
),
if (_selectedBook != null)
MaterialPage(
child: DetailsRouterPage(
id: _selectedBook!,
onPopPage: () {
_selectedBook = null;
notifyListeners();
},
),
),
],
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
_selectedBook = null;
notifyListeners();
return true;
},
);
}
void _selectBook(String id) {
_selectedBook = id;
notifyListeners();
}
@override
Future<bool> popRoute() {
if (_selectedBook == null) {
return SynchronousFuture<bool>(false);
}
_selectedBook = null;
notifyListeners();
return SynchronousFuture<bool>(true);
}
@override
AppRoutePath get currentConfiguration {
if (_selectedBook != null) {
return AppRoutePath.details(_selectedBook!);
}
return AppRoutePath.home();
}
@override
Future<void> setNewRoutePath(AppRoutePath configuration) {
if (configuration.isHomePage) {
_selectedBook = null;
} else {
_selectedBook = configuration.id;
}
return SynchronousFuture<void>(null);
}
}
// 路由信息解析器
class AppRouteInformationParser extends RouteInformationParser<AppRoutePath> {
@override
Future<AppRoutePath> parseRouteInformation(
RouteInformation routeInformation) async {
final uri = Uri.parse(routeInformation.location!);
if (uri.pathSegments.isEmpty) {
return AppRoutePath.home();
}
if (uri.pathSegments.length == 2) {
return AppRoutePath.details(uri.pathSegments[1]);
}
return AppRoutePath.home();
}
@override
RouteInformation? restoreRouteInformation(AppRoutePath configuration) {
if (configuration.isHomePage) {
return const RouteInformation(location: '/');
}
if (configuration.isDetailsPage) {
return RouteInformation(location: '/details/${configuration.id}');
}
return null;
}
}
class ModernRouterApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Modern Router Demo',
routerDelegate: AppRouterDelegate(),
routeInformationParser: AppRouteInformationParser(),
);
}
}
class HomeRouterPage extends StatelessWidget {
final Function(String) onBookTapped;
const HomeRouterPage({Key? key, required this.onBookTapped}) : super(key: key);
@override
Widget build(BuildContext context) {
final books = [
{'id': '1', 'title': 'Flutter入门', 'author': '张三'},
{'id': '2', 'title': 'Dart编程', 'author': '李四'},
{'id': '3', 'title': '移动开发', 'author': '王五'},
];
return Scaffold(
appBar: AppBar(title: Text('图书列表')),
body: ListView.builder(
itemCount: books.length,
itemBuilder: (context, index) {
final book = books[index];
return ListTile(
title: Text(book['title']!),
subtitle: Text('作者: ${book['author']}'),
onTap: () => onBookTapped(book['id']!),
);
},
),
);
}
}
class DetailsRouterPage extends StatelessWidget {
final String id;
final Function() onPopPage;
const DetailsRouterPage({Key? key, required this.id, required this.onPopPage}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('图书详情')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('图书ID: $id', style: TextStyle(fontSize: 18)),
SizedBox(height: 20),
ElevatedButton(
onPressed: onPopPage,
child: Text('返回'),
),
],
),
),
);
}
}
Tab导航 #
使用TabBar和TabBarView实现标签页导航:
dart
class TabNavigationDemo extends StatefulWidget {
@override
_TabNavigationDemoState createState() => _TabNavigationDemoState();
}
class _TabNavigationDemoState extends State<TabNavigationDemo> with TickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Tab导航演示'),
bottom: TabBar(
controller: _tabController,
tabs: [
Tab(text: '首页', icon: Icon(Icons.home)),
Tab(text: '搜索', icon: Icon(Icons.search)),
Tab(text: '个人', icon: Icon(Icons.person)),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
HomePageTab(),
SearchPageTab(),
ProfilePageTab(),
],
),
);
}
}
class HomePageTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.home, size: 100, color: Colors.blue),
Text('首页内容', style: TextStyle(fontSize: 24)),
],
),
);
}
}
class SearchPageTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.search, size: 100, color: Colors.green),
Text('搜索内容', style: TextStyle(fontSize: 24)),
],
),
);
}
}
class ProfilePageTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.person, size: 100, color: Colors.orange),
Text('个人内容', style: TextStyle(fontSize: 24)),
],
),
);
}
}
抽屉导航 #
使用Drawer实现侧滑菜单导航:
dart
class DrawerNavigationDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('抽屉导航演示'),
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [
CircleAvatar(
radius: 30,
backgroundColor: Colors.white,
child: Icon(Icons.person, size: 40),
),
SizedBox(height: 10),
Text(
'用户名',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
),
ListTile(
leading: Icon(Icons.home),
title: Text('首页'),
onTap: () {
Navigator.pop(context);
// 处理首页逻辑
},
),
ListTile(
leading: Icon(Icons.person),
title: Text('个人资料'),
onTap: () {
Navigator.pop(context);
// 处理个人资料逻辑
},
),
ListTile(
leading: Icon(Icons.settings),
title: Text('设置'),
onTap: () {
Navigator.pop(context);
// 处理设置逻辑
},
),
Divider(),
ListTile(
leading: Icon(Icons.info),
title: Text('关于'),
onTap: () {
Navigator.pop(context);
// 处理关于逻辑
},
),
],
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('主页面内容', style: TextStyle(fontSize: 24)),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => Scaffold.of(context).openDrawer(),
child: Text('打开抽屉'),
),
],
),
),
);
}
}
导航历史管理 #
替换当前路由 #
dart
class NavigationHistoryDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('导航历史管理')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
// 替换当前路由,用户按返回键时会跳过被替换的页面
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => ReplacementPage()),
);
},
child: Text('替换当前页面'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// 移除路由栈中的多个页面
Navigator.pushNamedAndRemoveUntil(
context,
'/profile',
ModalRoute.withName('/'), // 移除到首页为止的所有页面
);
},
child: Text('跳转并清除历史'),
),
],
),
),
);
}
}
class ReplacementPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('被替换的页面')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('这个页面会替换前一个页面'),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('返回(会回到首页)'),
),
],
),
),
);
}
}
拦截返回按钮 #
dart
class BackButtonInterceptorDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// 返回true允许返回,返回false阻止返回
bool confirm = await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('确认退出'),
content: Text('您确定要退出应用吗?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text('取消'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text('确定'),
),
],
),
);
return confirm;
},
child: Scaffold(
appBar: AppBar(title: Text('返回按钮拦截')),
body: Center(
child: Text('尝试按返回键'),
),
),
);
}
}
嵌套导航 #
在复杂应用中,可能需要在页面内部实现子导航:
dart
class NestedNavigationDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('嵌套导航'),
bottom: TabBar(
tabs: [
Tab(text: '列表'),
Tab(text: '网格'),
],
),
),
body: TabBarView(
children: [
ListViewPage(),
GridViewPage(),
],
),
),
);
}
}
class ListViewPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
padding: EdgeInsets.all(16.0),
children: [
for (int i = 1; i <= 10; i++)
Card(
child: ListTile(
title: Text('项目 $i'),
subtitle: Text('这是第 $i 个项目'),
trailing: Icon(Icons.arrow_forward_ios),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ItemDetailPage(itemId: i),
),
);
},
),
),
],
);
}
}
class GridViewPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GridView.builder(
padding: EdgeInsets.all(16.0),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemCount: 8,
itemBuilder: (context, index) {
return Card(
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ItemDetailPage(itemId: index + 1),
),
);
},
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.grid_3x3, size: 50),
Text('项目 ${index + 1}'),
],
),
),
),
);
},
);
}
}
class ItemDetailPage extends StatelessWidget {
final int itemId;
const ItemDetailPage({Key? key, required this.itemId}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('项目 $itemId 详情')),
body: Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.info, size: 80, color: Colors.blue),
SizedBox(height: 20),
Text(
'项目 $itemId 详情页面',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
SizedBox(height: 20),
Text(
'这里显示项目 $itemId 的详细信息',
textAlign: TextAlign.center,
),
SizedBox(height: 30),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('返回'),
),
],
),
),
),
);
}
}
导航最佳实践 #
1. 使用命名路由进行组织 #
对于大型应用,建议使用命名路由来组织页面导航:
dart
class AppRoutes {
static const String home = '/';
static const String profile = '/profile';
static const String settings = '/settings';
static const String detail = '/detail';
static Map<String, WidgetBuilder> get routes => {
home: (context) => HomePage(),
profile: (context) => ProfilePage(),
settings: (context) => SettingsPage(),
};
}
2. 合理使用导航器键 #
在某些情况下,可能需要全局访问导航器:
dart
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
home: HomePage(),
);
}
}
// 在应用的任何地方导航
void navigateToProfile() {
navigatorKey.currentState?.pushNamed('/profile');
}
3. 页面间数据传递的最佳实践 #
dart
// 定义路由参数类
class DetailPageArguments {
final String title;
final String content;
final int id;
DetailPageArguments({
required this.title,
required this.content,
required this.id,
});
}
// 在路由中使用
onGenerateRoute: (settings) {
if (settings.name == '/detail') {
final arguments = settings.arguments as DetailPageArguments;
return MaterialPageRoute(
builder: (context) => DetailPage(
title: arguments.title,
content: arguments.content,
id: arguments.id,
),
);
}
// ... 其他路由
return null;
},
总结 #
Flutter提供了多种导航方式来满足不同场景的需求:
- 基础导航:使用MaterialPageRoute和CupertinoPageRoute进行基本页面跳转
- 参数传递:通过路由参数在页面间传递数据
- 命名路由:为大型应用提供结构化的路由管理
- 现代Router API:提供更灵活的路由控制
- Tab导航:实现标签页切换
- 抽屉导航:提供侧滑菜单
- 导航历史管理:控制页面栈行为
选择合适的导航方式对于构建用户体验良好的Flutter应用至关重要。在实际开发中,应根据应用的复杂度和需求选择最适合的导航方案。