掌握页面导航技术,构建真正的多页面Flutter应用。
你好,欢迎回到《Flutter入门到精通》专栏。在上一讲中,我们学习了如何使用ListView和GridView展示动态数据。现在,让我们来学习如何将多个页面连接起来,构建一个完整的、可以导航的多页面应用。
一、导航基础:Navigator 与 Route
在Flutter中,导航由两个核心类管理:
-
Navigator:管理一组Route对象的Widget
-
Route:表示应用中的一个"屏幕"或"页面"
你可以把Navigator想象成一个栈管理器,Route就是栈中的页面。
1.1 基础导航方法
跳转到新页面
dart
Navigator.push( context, MaterialPageRoute(builder: (context) => NewPage()), );
返回上一页
dart
Navigator.pop(context);
带返回值的导航
dart
// 在第一个页面中跳转并等待返回值 final result = await Navigator.push( context, MaterialPageRoute(builder: (context) => SelectionPage()), ); // 在第二个页面中返回数据 Navigator.pop(context, '选中的数据');
二、静态路由:基本导航操作
2.1 基础跳转示例
让我们创建一个简单的多页面应用来演示基本导航:
main.dart
dart
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '导航演示',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(), // 应用启动页面
);
}
}
home_page.dart
dart
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('首页'),
backgroundColor: Colors.blue,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
// 跳转到详情页
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailPage(
title: '来自首页的详情',
content: '这是从首页传递过来的内容。',
),
),
);
},
child: const Text('跳转到详情页'),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// 跳转到设置页
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SettingsPage(),
),
);
},
child: const Text('跳转到设置页'),
),
],
),
),
);
}
}
detail_page.dart
dart
class DetailPage extends StatelessWidget {
final String title;
final String content;
const DetailPage({
super.key,
required this.title,
required this.content,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
backgroundColor: Colors.green,
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
content,
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// 返回上一页
Navigator.pop(context);
},
child: const Text('返回'),
),
],
),
),
);
}
}
settings_page.dart
dart
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('设置'),
backgroundColor: Colors.orange,
),
body: ListView(
children: [
ListTile(
leading: const Icon(Icons.person),
title: const Text('个人资料'),
onTap: () {
// 跳转到个人资料页
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProfilePage(),
),
);
},
),
ListTile(
leading: const Icon(Icons.notifications),
title: const Text('通知设置'),
onTap: () {
// 跳转到通知设置页
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const NotificationSettingsPage(),
),
);
},
),
ListTile(
leading: const Icon(Icons.security),
title: const Text('隐私设置'),
onTap: () {
// 跳转到隐私设置页
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const PrivacySettingsPage(),
),
);
},
),
],
),
);
}
}
2.2 带返回值的导航
这是一个非常实用的功能,比如从列表中选择一项:
selection_page.dart
dart
class SelectionPage extends StatefulWidget {
const SelectionPage({super.key});
@override
State<SelectionPage> createState() => _SelectionPageState();
}
class _SelectionPageState extends State<SelectionPage> {
String? _selectedItem;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('选择项目'),
),
body: ListView(
children: [
_buildSelectionItem('选项 A', 'A'),
_buildSelectionItem('选项 B', 'B'),
_buildSelectionItem('选项 C', 'C'),
_buildSelectionItem('选项 D', 'D'),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _selectedItem != null
? () {
// 返回选中的值
Navigator.pop(context, _selectedItem);
}
: null,
child: const Icon(Icons.check),
),
);
}
Widget _buildSelectionItem(String title, String value) {
return Card(
margin: const EdgeInsets.all(8),
color: _selectedItem == value ? Colors.blue[50] : null,
child: ListTile(
title: Text(title),
leading: Radio<String>(
value: value,
groupValue: _selectedItem,
onChanged: (String? value) {
setState(() {
_selectedItem = value;
});
},
),
onTap: () {
setState(() {
_selectedItem = value;
});
},
),
);
}
}
在首页中使用:
dart
ElevatedButton(
onPressed: () async {
// 等待选择页面返回结果
final selectedValue = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SelectionPage()),
);
// 显示选择结果
if (selectedValue != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('你选择了: $selectedValue')),
);
}
},
child: const Text('打开选择页面'),
),
三、命名路由:更清晰的导航管理
对于复杂的应用,使用命名路由可以让导航逻辑更清晰、易于维护。
3.1 配置命名路由
在MaterialApp中配置路由表:
main.dart
dart
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '命名路由演示',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// 定义应用的初始路由
initialRoute: '/',
// 配置路由表
routes: {
'/': (context) => const HomePage(),
'/detail': (context) => const DetailPage(
title: '默认标题',
content: '默认内容',
),
'/settings': (context) => const SettingsPage(),
'/profile': (context) => const ProfilePage(),
'/notifications': (context) => const NotificationSettingsPage(),
'/privacy': (context) => const PrivacySettingsPage(),
},
);
}
}
3.2 使用命名路由导航
dart
// 简单的命名路由跳转
Navigator.pushNamed(context, '/settings');
// 带参数的命名路由跳转
Navigator.pushNamed(
context,
'/detail',
arguments: {
'title': '动态标题',
'content': '动态内容',
},
);
3.3 在路由页面中接收参数
修改DetailPage来接收命名路由的参数:
dart
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
@override
Widget build(BuildContext context) {
// 从路由参数中获取数据
final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>?;
final title = args?['title'] ?? '默认标题';
final content = args?['content'] ?? '默认内容';
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text(content),
ElevatedButton(
onPressed: () => Navigator.pop(context),
child: const Text('返回'),
),
],
),
),
);
}
}
3.4 更优雅的参数传递
创建一个路由参数类来管理参数:
route_arguments.dart
dart
// 详情页参数
class DetailPageArguments {
final String title;
final String content;
DetailPageArguments({
required this.title,
required this.content,
});
}
// 个人资料页参数
class ProfilePageArguments {
final String userId;
final bool isEditable;
ProfilePageArguments({
required this.userId,
this.isEditable = false,
});
}
使用类型安全的参数:
dart
// 传递参数
Navigator.pushNamed(
context,
'/detail',
arguments: DetailPageArguments(
title: '类型安全的标题',
content: '类型安全的内容',
),
);
// 接收参数
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
@override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as DetailPageArguments;
return Scaffold(
appBar: AppBar(title: Text(args.title)),
body: Text(args.content),
);
}
}
四、导航高级技巧
4.1 清除导航栈
有时候我们需要跳转到新页面时清除所有历史记录:
dart
// 跳转到新页面并清除所有历史记录(用户无法返回) Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (context) => const LoginPage()), (route) => false, // 移除所有现有路由 ); // 跳转到新页面并保留首页 Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (context) => const MainPage()), (route) => route.isFirst, // 只保留第一个路由(首页) );
4.2 替换当前路由
用新页面替换当前页面:
dart
Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => const NewPage()), ); // 命名路由版本 Navigator.pushReplacementNamed(context, '/newpage');
4.3 弹出到指定页面
返回到导航栈中的特定页面:
dart
// 弹出直到找到首页
Navigator.popUntil(context, (route) => route.isFirst);
// 弹出直到找到指定名称的路由
Navigator.popUntil(context, ModalRoute.withName('/home'));
五、综合实战:完整的用户流程
让我们创建一个完整的用户认证流程来演示复杂的导航场景:
auth_flow_example.dart
dart
class AuthFlowExample extends StatelessWidget {
const AuthFlowExample({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '用户认证流程',
theme: ThemeData(primarySwatch: Colors.blue),
initialRoute: '/',
routes: {
'/': (context) => const SplashPage(),
'/login': (context) => const LoginPage(),
'/register': (context) => const RegisterPage(),
'/forgot-password': (context) => const ForgotPasswordPage(),
'/home': (context) => const MainHomePage(),
'/profile': (context) => const UserProfilePage(),
},
);
}
}
class SplashPage extends StatefulWidget {
const SplashPage({super.key});
@override
State<SplashPage> createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage> {
@override
void initState() {
super.initState();
_checkAuthStatus();
}
void _checkAuthStatus() async {
// 模拟检查用户登录状态
await Future.delayed(const Duration(seconds: 2));
final isLoggedIn = false; // 这里应该是实际的认证检查
if (mounted) {
if (isLoggedIn) {
// 已登录,跳转到首页并清除启动页
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => const MainHomePage()),
(route) => false,
);
} else {
// 未登录,跳转到登录页并清除启动页
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => const LoginPage()),
(route) => false,
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const CircularProgressIndicator(color: Colors.white),
const SizedBox(height: 20),
Text(
'加载中...',
style: TextStyle(color: Colors.white, fontSize: 16),
),
],
),
),
);
}
}
class LoginPage extends StatelessWidget {
const LoginPage({super.key});
void _performLogin(BuildContext context) async {
// 显示加载指示器
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const Center(child: CircularProgressIndicator()),
);
// 模拟登录过程
await Future.delayed(const Duration(seconds: 2));
// 关闭加载指示器
Navigator.pop(context);
// 登录成功,跳转到首页
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => const MainHomePage()),
(route) => false,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('登录')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(decoration: const InputDecoration(labelText: '用户名')),
const SizedBox(height: 16),
TextField(decoration: const InputDecoration(labelText: '密码'), obscureText: true),
const SizedBox(height: 32),
ElevatedButton(
onPressed: () => _performLogin(context),
child: const Text('登录'),
),
const SizedBox(height: 16),
TextButton(
onPressed: () {
Navigator.pushNamed(context, '/register');
},
child: const Text('还没有账号?立即注册'),
),
TextButton(
onPressed: () {
Navigator.pushNamed(context, '/forgot-password');
},
child: const Text('忘记密码?'),
),
],
),
),
);
}
}
class MainHomePage extends StatelessWidget {
const MainHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('首页'),
actions: [
IconButton(
icon: const Icon(Icons.person),
onPressed: () {
Navigator.pushNamed(context, '/profile');
},
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('欢迎来到主页!'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// 模拟退出登录
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => const LoginPage()),
(route) => false,
);
},
child: const Text('退出登录'),
),
],
),
),
);
}
}
导航最佳实践
-
保持导航简洁:避免过深的导航层级
-
使用命名路由:便于维护和团队协作
-
类型安全:为路由参数创建专门的类
-
错误处理:处理导航失败的情况
-
用户体验:提供清晰的导航反馈
结语
恭喜!通过本讲的学习,你已经掌握了Flutter中页面导航的核心技术。从基础跳转到复杂的命名路由,从简单传参到完整的用户流程,你现在可以构建真正的多页面应用了。
记住这些关键导航模式:
-
push/pop:基本的页面跳转和返回
-
命名路由:适合复杂应用的清晰结构
-
pushAndRemoveUntil:实现登录流程等场景
-
带返回值导航:用于选择和数据传递
在下一讲中,我们将进入Flutter开发的一个重要分水岭——状态管理。我们将深入学习StatefulWidget的状态管理,并介绍Provider等高级状态管理方案。
147

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



