当前位置:   article > 正文

[Flutter]页面跳转和传值_flutter 页面跳转

flutter 页面跳转

一、页面跳转

1.基本页面跳转

Navigator 介绍

Flutter 中,Navigator 是一个管理应用视图(页面)的组件,它使用栈(Stack)的方式来控制页面的切换。每当你跳转到一个新页面时,Navigator 会将新页面的 Route 压栈(push),当你返回到之前的页面时,它会将当前页面的 Route 出栈(pop)。

为了使用 Navigator 进行页面跳转,我们需要使用 BuildContext,它表示当前 widget 在 widget 树中的位置。BuildContext 是用于与 Navigator 进行交互的必要参数。

Navigator.push 方法

Navigator.push 方法用于将新的 Route 压入栈中,从而导航到新页面。

  1. Navigator.push(
  2. context,
  3. MaterialPageRoute(builder: (context) => NewPage()),
  4. );

或这种写法

  1. Navigator.push(context, MaterialPageRoute(
  2. builder: (context) {
  3. return NewPage();
  4. },
  5. ));

Navigator.pop 方法

Navigator.pop 方法用于将栈顶的 Route 弹出,返回到前一个页面。

Navigator.pop(context);

MaterialPageRoute 和页面跳转动画

MaterialPageRoute 是一种模态路由,它会根据目标平台的规范,为页面切换提供适当的动画。在 Android 上,它通常是一个从屏幕底部向上滑入的动画,而在 iOS 上,它通常是一个从屏幕右侧滑入的动画。

无参数页面跳转示例代码

  1. import 'package:flutter/material.dart';
  2. void main() {
  3. runApp(MaterialApp(
  4. title: 'Navigation Basics',
  5. home: HomePage(),
  6. ));
  7. }
  8. class HomePage extends StatelessWidget {
  9. @override
  10. Widget build(BuildContext context) {
  11. return Scaffold(
  12. appBar: AppBar(
  13. title: Text('Home Page'),
  14. ),
  15. body: Center(
  16. child: ElevatedButton(
  17. child: Text('Open New Page'),
  18. onPressed: () {
  19. // 使用 Navigator.push 方法来跳转到新页面
  20. Navigator.push(
  21. context,
  22. MaterialPageRoute(builder: (context) => NewPage()),
  23. );
  24. },
  25. ),
  26. ),
  27. );
  28. }
  29. }
  30. class NewPage extends StatelessWidget {
  31. @override
  32. Widget build(BuildContext context) {
  33. return Scaffold(
  34. appBar: AppBar(
  35. title: Text('New Page'),
  36. ),
  37. body: Column(
  38. children: [
  39. Text('Welcome to the new page!'),
  40. TextButton(
  41. onPressed: () {
  42. Navigator.pop(context);
  43. },
  44. child: Text("pop"))
  45. ],
  46. ),
  47. );
  48. }
  49. }

2.命名路由和路由表

命名路由介绍

命名路由是一种用于管理页面导航的技术,它允许你为每个页面分配一个唯一的名称,并通过这些名称在应用程序中进行页面之间的导航。命名路由,由一对字符串(路由名称)和对应的屏幕(或称为页面/视图)组成。

命名路由的好处

  • 提高代码可维护性命名路由使得路由和它们对应的屏幕解耦,这让查找和修改特定路由相关的代码变得更加容易。
  • 简化路由管理当应用的结构变得更为复杂时,使用命名路由可以帮助集中管理路由,而不是在代码中散布大量的 Navigator.push 和 MaterialPageRoute

配置命名路由

我们可以在 MaterialApp 的 routes 属性中定义所有的命名路由。routes 是一个 Map,它的键是字符串(路由的名称),而值是对应的构造器函数,返回相应的页面 Widget。

  1. MaterialApp(
  2. title: 'Navigation with Named Routes',
  3. // 初始路由,应用启动时加载的路由
  4. initialRoute: '/',
  5. // 定义命名路由
  6. routes: {
  7. '/': (context) => HomePage(),
  8. '/newPage': (context) => NewPage(),
  9. '/thirdPage': (context) => ThirdPage(),
  10. },
  11. )

Navigator.pushNamed 方法

要使用命名路由进行页面跳转,可以调用 Navigator.pushNamed 方法,并传入对应的路由名称。

Navigator.pushNamed(context, '/newPage');

Navigator.pop 方法

Navigator.pop 方法用于将栈顶的 Route 弹出,返回到前一个页面。

Navigator.pop(context);

Navigator.popAndPushNamed 方法

Navigator.popAndPushNamed 方法用于从当前页面返回到上一个页面,并立即导航到指定的命名路由。

该方法的作用是先执行 Navigator.pop 方法返回到上一个页面,然后立即执行 Navigator.pushNamed 方法导航到新的命名路由。

Navigator.popAndPushNamed(context, '/thirdPage');

配置和使用命名路由示例代码

  1. import 'package:flutter/material.dart';
  2. void main() {
  3. runApp(MaterialApp(
  4. title: 'Navigation with Named Routes',
  5. // 初始路由,应用启动时加载的路由
  6. initialRoute: '/',
  7. // 定义命名路由
  8. routes: {
  9. '/': (context) => HomePage(),
  10. '/newPage': (context) => NewPage(),
  11. '/thirdPage': (context) => ThirdPage(),
  12. },
  13. ));
  14. }
  15. class HomePage extends StatelessWidget {
  16. @override
  17. Widget build(BuildContext context) {
  18. return Scaffold(
  19. appBar: AppBar(
  20. title: Text('Home Page'),
  21. ),
  22. body: Center(
  23. child: ElevatedButton(
  24. child: Text('Open New Page'),
  25. // 使用命名路由进行页面跳转
  26. onPressed: () {
  27. Navigator.pushNamed(context, '/newPage');
  28. },
  29. ),
  30. ),
  31. );
  32. }
  33. }
  34. class NewPage extends StatelessWidget {
  35. @override
  36. Widget build(BuildContext context) {
  37. return Scaffold(
  38. appBar: AppBar(
  39. title: Text('New Page'),
  40. ),
  41. body: Column(
  42. children: [
  43. Text('Welcome to the new page!'),
  44. TextButton(
  45. onPressed: () {
  46. Navigator.popAndPushNamed(context, '/thirdPage');
  47. },
  48. child: Text("popAndPushNamed"))
  49. ],
  50. ),
  51. );
  52. }
  53. }
  54. class ThirdPage extends StatelessWidget {
  55. @override
  56. Widget build(BuildContext context) {
  57. return Scaffold(
  58. appBar: AppBar(
  59. title: Text('Third Page'),
  60. ),
  61. body: Column(
  62. children: [
  63. Text('Welcome to the Third page!'),
  64. TextButton(
  65. onPressed: () {
  66. Navigator.pop(context);
  67. },
  68. child: Text("pop"))
  69. ],
  70. ),
  71. );
  72. }
  73. }

二、页面传值

1.push时向新页面传递数据

(1).通过构造函数传递数据

最直接的方式是通过目标页面的构造函数直接传递数据。

  1. import 'package:flutter/material.dart';
  2. void main() {
  3. runApp(MaterialApp(
  4. home: HomePage(),
  5. ));
  6. }
  7. class HomePage extends StatelessWidget {
  8. @override
  9. Widget build(BuildContext context) {
  10. return Scaffold(
  11. appBar: AppBar(
  12. title: Text('Home Page'),
  13. ),
  14. body: Center(
  15. child: ElevatedButton(
  16. child: Text('Pass Data to New Page'),
  17. onPressed: () {
  18. // 通过构造函数直接传递数据
  19. Navigator.push(
  20. context,
  21. MaterialPageRoute(
  22. builder: (context) => NewPage(data: 'Hello from Home Page!'),
  23. ),
  24. );
  25. },
  26. ),
  27. ),
  28. );
  29. }
  30. }
  31. class NewPage extends StatelessWidget {
  32. final String data;
  33. // 接收数据的构造函数
  34. NewPage({required this.data});
  35. @override
  36. Widget build(BuildContext context) {
  37. return Scaffold(
  38. appBar: AppBar(
  39. title: Text('New Page'),
  40. ),
  41. body: Center(
  42. child: Text(data), // 显示传递过来的数据
  43. ),
  44. );
  45. }
  46. }

(2).使用 MaterialPageRoute 的 arguments 属性

另一种传递数据的方式是使用 MaterialPageRoute 的 arguments 属性,这在使用命名路由时尤其有用。

  1. import 'package:flutter/material.dart';
  2. void main() {
  3. runApp(MaterialApp(
  4. // 初始路由,应用启动时加载的路由
  5. initialRoute: '/',
  6. // 定义命名路由
  7. routes: {
  8. '/': (context) => HomePage(),
  9. '/newPage': (context) => NewPage(),
  10. },
  11. ));
  12. }
  13. class HomePage extends StatelessWidget {
  14. @override
  15. Widget build(BuildContext context) {
  16. return Scaffold(
  17. appBar: AppBar(
  18. title: Text('Home Page'),
  19. ),
  20. body: Center(
  21. child: ElevatedButton(
  22. child: Text('Pass Data to New Page'),
  23. onPressed: () {
  24. Navigator.pushNamed(
  25. context,
  26. '/newPage',
  27. arguments: 'Hello from Home Page!',
  28. );
  29. },
  30. ),
  31. ),
  32. );
  33. }
  34. }
  35. class NewPage extends StatelessWidget {
  36. @override
  37. Widget build(BuildContext context) {
  38. // 获取传递过来的数据
  39. final String data = ModalRoute.of(context)!.settings.arguments as String;
  40. return Scaffold(
  41. appBar: AppBar(
  42. title: Text('New Page'),
  43. ),
  44. body: Center(
  45. child: Text(data), // 显示传递过来的数据
  46. ),
  47. );
  48. }
  49. }

2.pop时返回数据给前一个页面

使用 Navigator.pop 返回结果

当从一个页面返回到前一个页面时,可以通过 Navigator.pop 方法返回数据:

  1. // 假设这是 NewPage 中的一个按钮,当点击时返回数据给前一个页面
  2. ElevatedButton(
  3. onPressed: () {
  4. Navigator.pop(context, 'Result from New Page');
  5. },
  6. child: Text('Return Data to Home Page'),
  7. ),

Navigator.push 和 await 结合使用

你可以使用 await 关键字等待一个页面返回结果:

  1. // ... HomePage 中的按钮点击事件
  2. onPressed: () async {
  3. final result = await Navigator.push(
  4. context,
  5. MaterialPageRoute(builder: (context) => NewPage()),
  6. );
  7. // 使用 ScaffoldMessenger 显示返回的结果
  8. ScaffoldMessenger.of(context).showSnackBar(
  9. SnackBar(content: Text(result?.toString() ?? 'No result')),
  10. );
  11. },

  1. onPressed: () async {
  2. final result = await Navigator.pushNamed(
  3. context,
  4. '/newPage',
  5. arguments: 'Hello from Home Page!',
  6. );
  7. // 使用 ScaffoldMessenger 显示返回的结果
  8. ScaffoldMessenger.of(context).showSnackBar(
  9. SnackBar(content: Text(result?.toString() ?? 'No result')),
  10. );
  11. },
  12. ),

使用 PopScope 拦截系统返回按钮的行为

如果你不显式的调用Navigator.pop(context, 'xxx'),就拿不到回传结果。比如你从系统导航上点击返回按钮,就没数据传递回去。

如果一定任何返回都回传值,就需要定义导航栏,或者通过使用 PopScope widget 来拦截系统返回按钮的行为,并执行自定义的操作。

  1. class NewPage extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. return Scaffold(
  5. appBar: AppBar(
  6. title: Text('New Page'),
  7. ),
  8. body: PopScope(
  9. canPop: false, // 使用canPop提前禁用pop
  10. onPopInvoked: (bool didPop) {
  11. // canPop被设置为false时。didPop参数表示返回导航是否成功。
  12. // `didPop`参数会告诉你路由是否成功地pop出
  13. if (!didPop) {
  14. // 在这里执行你的操作,比如返回数据. 前面不禁用pop的话,这里就会pop两次了。
  15. Navigator.pop(context, 'Custom back button result');
  16. }
  17. },
  18. child: Column(
  19. children: [
  20. TextButton(
  21. onPressed: () {
  22. Navigator.pop(context, 'Result from New Page');
  23. },
  24. child: Text("pop"))
  25. ],
  26. ),
  27. ),
  28. );
  29. }
  30. }

注意:这里用多个地方调用Navigator.pop,从不同地方返回时,回传的值也会不同。如果要求回传的数据一致,就将Navigator.pop方法抽离放到一个方法中,多个返回位置调用同一个方法回传同样的数据。

三、路由生成钩子(onGenerateRoute)

在Flutter中,onGenerateRoute是一个非常强大的钩子,允许开发者对路由进行自定义操作。它在MaterialAppCupertinoApp中定义,并在导航到命名路由时被调用,特别是当使用Navigator.pushNamed时。它可以用于动态生成路由,传递参数到新页面,甚至处理未知的路由。

下面是一个使用onGenerateRoute的示例,其中包括了处理动态路由和传递参数到未知页面的代码:

  1. import 'package:flutter/material.dart';
  2. void main() {
  3. runApp(MyApp());
  4. }
  5. class MyApp extends StatelessWidget {
  6. @override
  7. Widget build(BuildContext context) {
  8. return MaterialApp(
  9. // 应用初始路由
  10. initialRoute: '/',
  11. // onGenerateRoute 用于处理动态路由
  12. onGenerateRoute: (RouteSettings settings) {
  13. // 获取传递过来的参数,如果参数为null,则提供一个空的Map
  14. final arguments = settings.arguments as Map<String, dynamic>? ?? {};
  15. // 根据 settings.name 处理不同路由
  16. switch (settings.name) {
  17. case '/':
  18. return MaterialPageRoute(builder: (context) => HomePage());
  19. case '/details':
  20. // 假设 DetailsPage 接受一个 'data' 参数
  21. final String data = arguments['data'] as String? ?? '默认值';
  22. return MaterialPageRoute(builder: (context) => DetailsPage(data: data));
  23. default:
  24. // 如果没有匹配的路由,返回到一个未知页面路由
  25. return MaterialPageRoute(builder: (context) => UnknownPage());
  26. }
  27. },
  28. );
  29. }
  30. }
  31. class HomePage extends StatefulWidget {
  32. const HomePage({super.key});
  33. @override
  34. State<HomePage> createState() => _HomePageState();
  35. }
  36. class _HomePageState extends State<HomePage> {
  37. String _data = "缺省值";
  38. @override
  39. Widget build(BuildContext context) {
  40. return Scaffold(
  41. appBar: AppBar(
  42. title: Text('首页'),
  43. ),
  44. body: Column(
  45. children: [
  46. Text(_data), // 显示传递到本页面的数据
  47. ElevatedButton(
  48. onPressed: () async {
  49. // 导航到详情页,并传递数据。同时,使用await等待详情页返回的结果
  50. final result = await Navigator.pushNamed(
  51. context,
  52. '/details',
  53. arguments: {'data': '这是一个秘密信息!'},
  54. );
  55. final arguments = result as Map<String, dynamic>? ?? {};
  56. setState(() {
  57. if (mounted) {
  58. _data = arguments["data"] as String? ?? "";
  59. }
  60. });
  61. },
  62. child: Text('前往详情页'),
  63. ),
  64. ],
  65. ),
  66. );
  67. }
  68. }
  69. class DetailsPage extends StatelessWidget {
  70. final String data;
  71. DetailsPage({required this.data});
  72. @override
  73. Widget build(BuildContext context) {
  74. return Scaffold(
  75. appBar: AppBar(
  76. title: Text('详情页'),
  77. ),
  78. body: Column(
  79. children: [
  80. Text(data), // 显示传递到本页面的数据
  81. TextButton(
  82. onPressed: () {
  83. // 回传数据数据
  84. Navigator.pop(context, {'data': '详情返回数据'});
  85. },
  86. child: Text("pop"))
  87. ],
  88. ),
  89. );
  90. }
  91. }
  92. class UnknownPage extends StatelessWidget {
  93. @override
  94. Widget build(BuildContext context) {
  95. return Scaffold(
  96. appBar: AppBar(
  97. title: Text('未知页面'),
  98. ),
  99. body: Center(
  100. child: Text('该路由名称不存在。'),
  101. ),
  102. );
  103. }
  104. }

 四、路由传值的安全性

1.验证传入数据

当通过路由传递数据时,重要的是要验证接收的数据是否符合预期。你可以使用类型检查、正则表达式或自定义验证函数来确保数据的有效性和安全性。

  1. bool isValidData(dynamic data) {
  2. // 在这里添加验证逻辑,例如类型检查、内容检查等
  3. return data is String && data.isNotEmpty;
  4. }

在使用数据之前,你可以调用这个函数来验证:

  1. if (isValidData(receivedData)) {
  2. // 数据有效,可以安全使用
  3. } else {
  4. // 数据无效,可以抛出异常或进行错误处理
  5. }

2.处理空值和异常

处理空值和异常是确保应用程序稳定性的重要部分。当你从路由接收数据时,应该始终假设这些数据可能为空或者不是预期的格式。下面是一些处理这些情况的策略:

处理空值

当你期望的数据可能为空时,可以使用Dart的null-aware运算符来优雅地处理:

String data = receivedData ?? '默认值';

或者在使用之前检查数据是否为null:

  1. if (receivedData != null) {
  2. // 使用 receivedData
  3. } else {
  4. // 处理空值情况,例如返回错误提示或设置默认值
  5. }

异常处理

如果数据转换或验证过程中可能抛出异常,你应该使用try-catch语句来捕获这些异常:

  1. try {
  2. // 尝试使用 receivedData
  3. } catch (e) {
  4. // 处理异常,例如记录日志、显示错误信息等
  5. }

五、使用 Provider 管理跨页面的状态

Provider 是一个流行的状态管理库,它依赖于 Flutter 的 InheritedWidget 来向下传递数据。它能够让你在 widget 树中跨越多个层级来传递和修改数据,而无需手动传递回调或数据。

使用 Provider,你可以在应用的顶层提供一个状态,然后在应用的任何其他部分访问或修改这个状态。这适用于跨多个页面传递数据,甚至是整个应用的状态管理。

详细使用参见另一文https://gamin.blog.csdn.net/article/details/136556092

1.使用 Provider 进行状态管理和传值

首先,你需要在 pubspec.yaml 文件中添加 provider 依赖项:

  1. dependencies:
  2. flutter:
  3. sdk: flutter
  4. provider: ^6.1.2 # 使用适合你的版本

然后,在应用顶层(即要包裹住MaterialApp)引入 Provider

  1. import 'package:flutter/material.dart';
  2. import 'package:provider/provider.dart';
  3. void main() {
  4. runApp(
  5. // 通过 MultiProvider 可以提供多个对象
  6. MultiProvider(
  7. providers: [
  8. // ChangeNotifierProvider 是 Provider 的一种,它可以响应通知
  9. ChangeNotifierProvider(create: (context) => DataProvider()),
  10. ],
  11. child: MyApp(),
  12. ),
  13. );
  14. }
  15. class MyApp extends StatelessWidget {
  16. @override
  17. Widget build(BuildContext context) {
  18. return MaterialApp(
  19. title: 'Flutter Demo',
  20. home: HomePage(),
  21. );
  22. }
  23. }
  1. // 定义一个继承自 ChangeNotifier 的数据模型,用来传递和响应变化
  2. class DataProvider extends ChangeNotifier {
  3. String _data = "初始数据";
  4. String get data => _data;
  5. void setData(String newData) {
  6. _data = newData;
  7. notifyListeners(); // 当更新数据时,通知监听的 widgets 进行重建
  8. }
  9. }

HomePage 中的按钮点击时,可以使用 Provider 来更新数据:

  1. class HomePage extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. // 使用 Provider.of 来获取最近的 DataProvider 实例
  5. final dataProvider = Provider.of<DataProvider>(context);
  6. return Scaffold(
  7. appBar: AppBar(
  8. title: Text('首页'),
  9. ),
  10. body: Center(
  11. child: ElevatedButton(
  12. onPressed: () {
  13. // 更新数据
  14. dataProvider.setData('更新的数据');
  15. // 导航到详情页
  16. Navigator.push(
  17. context,
  18. MaterialPageRoute(builder: (context) => DetailsPage()),
  19. );
  20. },
  21. child: Text('前往详情页并传递数据'),
  22. ),
  23. ),
  24. );
  25. }
  26. }
  27. class DetailsPage extends StatelessWidget {
  28. @override
  29. Widget build(BuildContext context) {
  30. // 监听 DataProvider,当数据变化时重建这个 widget
  31. final data = Provider.of<DataProvider>(context).data;
  32. return Scaffold(
  33. appBar: AppBar(
  34. title: Text('详情页'),
  35. ),
  36. body: Center(
  37. // 显示从 Provider 获取的数据
  38. child: Text(data),
  39. ),
  40. );
  41. }
  42. }

2.完整的Provider例子

下面是一个使用Provider进行状态管理和跨页面传值的完整示例,包括异常处理和空值检查:

首先,确保已经添加了provider依赖。

  1. // main.dart
  2. import 'package:flutter/material.dart';
  3. import 'package:provider/provider.dart';
  4. void main() {
  5. runApp(MyApp());
  6. }
  7. class MyApp extends StatelessWidget {
  8. @override
  9. Widget build(BuildContext context) {
  10. return ChangeNotifierProvider<DataModel>(
  11. create: (_) => DataModel(),
  12. child: MaterialApp(
  13. title: 'Flutter Demo',
  14. home: HomePage(),
  15. ),
  16. );
  17. }
  18. }
  19. class DataModel extends ChangeNotifier {
  20. String _data = '';
  21. String get data => _data;
  22. void updateData(String newData) {
  23. if (newData.isNotEmpty) {
  24. _data = newData;
  25. notifyListeners();
  26. } else {
  27. throw Exception('Data cannot be empty');
  28. }
  29. }
  30. }
  31. class HomePage extends StatelessWidget {
  32. @override
  33. Widget build(BuildContext context) {
  34. return Scaffold(
  35. appBar: AppBar(title: Text('Home')),
  36. body: Center(
  37. child: Consumer<DataModel>(
  38. builder: (context, dataModel, child) {
  39. return ElevatedButton(
  40. onPressed: () {
  41. try {
  42. dataModel.updateData('New Data from Home');
  43. Navigator.push(
  44. context,
  45. MaterialPageRoute(builder: (context) => DetailsPage()),
  46. );
  47. } catch (e) {
  48. // Handle the exception
  49. ScaffoldMessenger.of(context).showSnackBar(
  50. SnackBar(content: Text(e.toString())),
  51. );
  52. }
  53. },
  54. child: Text('Go to Details'),
  55. );
  56. },
  57. ),
  58. ),
  59. );
  60. }
  61. }
  62. class DetailsPage extends StatelessWidget {
  63. @override
  64. Widget build(BuildContext context) {
  65. return Scaffold(
  66. appBar: AppBar(title: Text('Details')),
  67. body: Center(
  68. child: Consumer<DataModel>(
  69. builder: (context, dataModel, child) {
  70. return Text(dataModel.data);
  71. },
  72. ),
  73. ),
  74. );
  75. }
  76. }

在这个例子中,DataModel 是一个简单的数据持有类,它通过Provider允许在应用程序的其他部分访问和修改数据。HomePage 设置新的数据,并导航到DetailsPageDetailsPage 显示当前的数据。如果尝试设置空的数据,DataModel 将抛出一个异常,该异常在HomePage中被捕获并显示为一个SnackBar

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/595803
推荐阅读
相关标签
  

闽ICP备14008679号