当前位置: 首页 > news >正文

第10讲:导航与路由:在页面间穿梭

掌握页面导航技术,构建真正的多页面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});@overrideWidget 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});@overrideWidget 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,});@overrideWidget 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});@overrideWidget 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});@overrideState<SelectionPage> createState() => _SelectionPageState();
}class _SelectionPageState extends State<SelectionPage> {String? _selectedItem;@overrideWidget 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});@overrideWidget 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});@overrideWidget 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});@overrideWidget 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});@overrideWidget 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});@overrideState<SplashPage> createState() => _SplashPageState();
}class _SplashPageState extends State<SplashPage> {@overridevoid 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,);}}}@overrideWidget 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,);}@overrideWidget 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});@overrideWidget 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('退出登录'),),],),),);}
}

导航最佳实践

  1. 保持导航简洁:避免过深的导航层级

  2. 使用命名路由:便于维护和团队协作

  3. 类型安全:为路由参数创建专门的类

  4. 错误处理:处理导航失败的情况

  5. 用户体验:提供清晰的导航反馈

结语

恭喜!通过本讲的学习,你已经掌握了Flutter中页面导航的核心技术。从基础跳转到复杂的命名路由,从简单传参到完整的用户流程,你现在可以构建真正的多页面应用了。

记住这些关键导航模式:

  • push/pop:基本的页面跳转和返回

  • 命名路由:适合复杂应用的清晰结构

  • pushAndRemoveUntil:实现登录流程等场景

  • 带返回值导航:用于选择和数据传递

在下一讲中,我们将进入Flutter开发的一个重要分水岭——状态管理。我们将深入学习StatefulWidget的状态管理,并介绍Provider等高级状态管理方案。

http://www.dtcms.com/a/557899.html

相关文章:

  • Linux 磁盘空间“消失”之谜:文件已删,空间却不释放?
  • P1990 覆盖墙壁(dp)
  • 计算机操作系统理论学习
  • 胶州建网站推广游戏的平台
  • 未来之窗昭和仙君(四十九)集成电路芯片生产管理出库——东方仙盟筑基期
  • 哈尔滨公司网站建设科技企业网站源码
  • 行业网站推广怎么做wordpress-3.7
  • Pandas 简介与安装
  • openpnp - 吸嘴的单独校准
  • 程序员除了做软件是不是就做网站县 两学一做网站
  • 企业网站制作公司有哪些口碑营销的本质是什么
  • 北京住房和建设部网站首页网站开发结构图
  • 最新电子电气架构(EEA)调研-4
  • Google Chrome (dev beta)
  • SimPy经典案例分析
  • 跨端开发实践:多端适配指南
  • 索引的知识总结
  • 外贸汽车网站制作设计类专业笔记本电脑推荐
  • 织梦网站网站上的图用美图秀秀做可以吗
  • WebMvcConfig 和 WebSecurityConfig 详解
  • 使用unity做网站商城网站建设运营协议书
  • 21.7 企业级监控与日志系统实战:Prometheus+Grafana+ELK全链路配置指南
  • wordpress网站搬家书画院网站源码
  • css属性使用手册
  • SAPMM修改物料评估类型后报错:M7115只是可能对有相同评估类进行倒记帐(本月1002,前月1007)
  • 现在主流的网站开发语言全网整合营销平台
  • 08数据展示:Grafana数据可视化工具
  • 做网站多少钱_西宁君博相约宿迁人才网
  • v-code-diff入口文件的配置
  • 北京网站优化推广效果网站备案 取消