Flutter 页面跳转和传值的实现
作者:风浅月明
一、页面跳转
1.基本页面跳转
Navigator 介绍
在 Flutter 中,Navigator
是一个管理应用视图(页面)的组件,它使用栈(Stack)的方式来控制页面的切换。每当你跳转到一个新页面时,Navigator
会将新页面的 Route
压栈(push),当你返回到之前的页面时,它会将当前页面的 Route
出栈(pop)。
为了使用 Navigator
进行页面跳转,我们需要使用 BuildContext
,它表示当前 widget 在 widget 树中的位置。BuildContext
是用于与 Navigator
进行交互的必要参数。
Navigator.push 方法
Navigator.push
方法用于将新的 Route
压入栈中,从而导航到新页面。
Navigator.push( context, MaterialPageRoute(builder: (context) => NewPage()), );
或这种写法
Navigator.push(context, MaterialPageRoute( builder: (context) { return NewPage(); }, ));
Navigator.pop 方法
Navigator.pop
方法用于将栈顶的 Route
弹出,返回到前一个页面。
Navigator.pop(context);
MaterialPageRoute 和页面跳转动画
MaterialPageRoute
是一种模态路由,它会根据目标平台的规范,为页面切换提供适当的动画。在 Android 上,它通常是一个从屏幕底部向上滑入的动画,而在 iOS 上,它通常是一个从屏幕右侧滑入的动画。
无参数页面跳转示例代码
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( title: 'Navigation Basics', home: HomePage(), )); } class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Home Page'), ), body: Center( child: ElevatedButton( child: Text('Open New Page'), onPressed: () { // 使用 Navigator.push 方法来跳转到新页面 Navigator.push( context, MaterialPageRoute(builder: (context) => NewPage()), ); }, ), ), ); } } class NewPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('New Page'), ), body: Column( children: [ Text('Welcome to the new page!'), TextButton( onPressed: () { Navigator.pop(context); }, child: Text("pop")) ], ), ); } }
2.命名路由和路由表
命名路由介绍
命名路由是一种用于管理页面导航的技术,它允许你为每个页面分配一个唯一的名称,并通过这些名称在应用程序中进行页面之间的导航。命名路由,由一对字符串(路由名称)和对应的屏幕(或称为页面/视图)组成。
命名路由的好处
- 提高代码可维护性:命名路由使得路由和它们对应的屏幕解耦,这让查找和修改特定路由相关的代码变得更加容易。
- 简化路由管理:当应用的结构变得更为复杂时,使用命名路由可以帮助集中管理路由,而不是在代码中散布大量的
Navigator.push
和MaterialPageRoute
。
配置命名路由
我们可以在 MaterialApp
的 routes
属性中定义所有的命名路由。routes
是一个 Map,它的键是字符串(路由的名称),而值是对应的构造器函数,返回相应的页面 Widget。
MaterialApp( title: 'Navigation with Named Routes', // 初始路由,应用启动时加载的路由 initialRoute: '/', // 定义命名路由 routes: { '/': (context) => HomePage(), '/newPage': (context) => NewPage(), '/thirdPage': (context) => ThirdPage(), }, )
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');
配置和使用命名路由示例代码
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( title: 'Navigation with Named Routes', // 初始路由,应用启动时加载的路由 initialRoute: '/', // 定义命名路由 routes: { '/': (context) => HomePage(), '/newPage': (context) => NewPage(), '/thirdPage': (context) => ThirdPage(), }, )); } class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Home Page'), ), body: Center( child: ElevatedButton( child: Text('Open New Page'), // 使用命名路由进行页面跳转 onPressed: () { Navigator.pushNamed(context, '/newPage'); }, ), ), ); } } class NewPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('New Page'), ), body: Column( children: [ Text('Welcome to the new page!'), TextButton( onPressed: () { Navigator.popAndPushNamed(context, '/thirdPage'); }, child: Text("popAndPushNamed")) ], ), ); } } class ThirdPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Third Page'), ), body: Column( children: [ Text('Welcome to the Third page!'), TextButton( onPressed: () { Navigator.pop(context); }, child: Text("pop")) ], ), ); } }
二、页面传值
1.push时向新页面传递数据
(1).通过构造函数传递数据
最直接的方式是通过目标页面的构造函数直接传递数据。
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( home: HomePage(), )); } class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Home Page'), ), body: Center( child: ElevatedButton( child: Text('Pass Data to New Page'), onPressed: () { // 通过构造函数直接传递数据 Navigator.push( context, MaterialPageRoute( builder: (context) => NewPage(data: 'Hello from Home Page!'), ), ); }, ), ), ); } } class NewPage extends StatelessWidget { final String data; // 接收数据的构造函数 NewPage({required this.data}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('New Page'), ), body: Center( child: Text(data), // 显示传递过来的数据 ), ); } }
(2).使用 MaterialPageRoute
的 arguments
属性
另一种传递数据的方式是使用 MaterialPageRoute
的 arguments
属性,这在使用命名路由时尤其有用。
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( // 初始路由,应用启动时加载的路由 initialRoute: '/', // 定义命名路由 routes: { '/': (context) => HomePage(), '/newPage': (context) => NewPage(), }, )); } class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Home Page'), ), body: Center( child: ElevatedButton( child: Text('Pass Data to New Page'), onPressed: () { Navigator.pushNamed( context, '/newPage', arguments: 'Hello from Home Page!', ); }, ), ), ); } } class NewPage extends StatelessWidget { @override Widget build(BuildContext context) { // 获取传递过来的数据 final String data = ModalRoute.of(context)!.settings.arguments as String; return Scaffold( appBar: AppBar( title: Text('New Page'), ), body: Center( child: Text(data), // 显示传递过来的数据 ), ); } }
2.pop时返回数据给前一个页面
使用 Navigator.pop
返回结果
当从一个页面返回到前一个页面时,可以通过 Navigator.pop
方法返回数据:
// 假设这是 NewPage 中的一个按钮,当点击时返回数据给前一个页面 ElevatedButton( onPressed: () { Navigator.pop(context, 'Result from New Page'); }, child: Text('Return Data to Home Page'), ),
Navigator.push
和 await
结合使用
你可以使用 await
关键字等待一个页面返回结果:
// ... HomePage 中的按钮点击事件 onPressed: () async { final result = await Navigator.push( context, MaterialPageRoute(builder: (context) => NewPage()), ); // 使用 ScaffoldMessenger 显示返回的结果 ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(result?.toString() ?? 'No result')), ); },
或
onPressed: () async { final result = await Navigator.pushNamed( context, '/newPage', arguments: 'Hello from Home Page!', ); // 使用 ScaffoldMessenger 显示返回的结果 ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(result?.toString() ?? 'No result')), ); }, ),
使用 PopScope 拦截系统返回按钮的行为
如果你不显式的调用Navigator.pop(context, 'xxx'),就拿不到回传结果。比如你从系统导航上点击返回按钮,就没数据传递回去。
如果一定任何返回都回传值,就需要定义导航栏,或者通过使用 PopScope widget 来拦截系统返回按钮的行为,并执行自定义的操作。
class NewPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('New Page'), ), body: PopScope( canPop: false, // 使用canPop提前禁用pop onPopInvoked: (bool didPop) { // canPop被设置为false时。didPop参数表示返回导航是否成功。 // `didPop`参数会告诉你路由是否成功地pop出 if (!didPop) { // 在这里执行你的操作,比如返回数据. 前面不禁用pop的话,这里就会pop两次了。 Navigator.pop(context, 'Custom back button result'); } }, child: Column( children: [ TextButton( onPressed: () { Navigator.pop(context, 'Result from New Page'); }, child: Text("pop")) ], ), ), ); } }
注意:这里用多个地方调用Navigator.pop,从不同地方返回时,回传的值也会不同。如果要求回传的数据一致,就将Navigator.pop方法抽离放到一个方法中,多个返回位置调用同一个方法回传同样的数据。
三、路由生成钩子(onGenerateRoute)
在Flutter中,onGenerateRoute
是一个非常强大的钩子,允许开发者对路由进行自定义操作。它在MaterialApp
或CupertinoApp
中定义,并在导航到命名路由时被调用,特别是当使用Navigator.pushNamed
时。它可以用于动态生成路由,传递参数到新页面,甚至处理未知的路由。
下面是一个使用onGenerateRoute
的示例,其中包括了处理动态路由和传递参数到未知页面的代码:
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( // 应用初始路由 initialRoute: '/', // onGenerateRoute 用于处理动态路由 onGenerateRoute: (RouteSettings settings) { // 获取传递过来的参数,如果参数为null,则提供一个空的Map final arguments = settings.arguments as Map<String, dynamic>? ?? {}; // 根据 settings.name 处理不同路由 switch (settings.name) { case '/': return MaterialPageRoute(builder: (context) => HomePage()); case '/details': // 假设 DetailsPage 接受一个 'data' 参数 final String data = arguments['data'] as String? ?? '默认值'; return MaterialPageRoute(builder: (context) => DetailsPage(data: data)); default: // 如果没有匹配的路由,返回到一个未知页面路由 return MaterialPageRoute(builder: (context) => UnknownPage()); } }, ); } } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { String _data = "缺省值"; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('首页'), ), body: Column( children: [ Text(_data), // 显示传递到本页面的数据 ElevatedButton( onPressed: () async { // 导航到详情页,并传递数据。同时,使用await等待详情页返回的结果 final result = await Navigator.pushNamed( context, '/details', arguments: {'data': '这是一个秘密信息!'}, ); final arguments = result as Map<String, dynamic>? ?? {}; setState(() { if (mounted) { _data = arguments["data"] as String? ?? ""; } }); }, child: Text('前往详情页'), ), ], ), ); } } class DetailsPage extends StatelessWidget { final String data; DetailsPage({required this.data}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('详情页'), ), body: Column( children: [ Text(data), // 显示传递到本页面的数据 TextButton( onPressed: () { // 回传数据数据 Navigator.pop(context, {'data': '详情返回数据'}); }, child: Text("pop")) ], ), ); } } class UnknownPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('未知页面'), ), body: Center( child: Text('该路由名称不存在。'), ), ); } }
四、路由传值的安全性
1.验证传入数据
当通过路由传递数据时,重要的是要验证接收的数据是否符合预期。你可以使用类型检查、正则表达式或自定义验证函数来确保数据的有效性和安全性。
bool isValidData(dynamic data) { // 在这里添加验证逻辑,例如类型检查、内容检查等 return data is String && data.isNotEmpty; }
在使用数据之前,你可以调用这个函数来验证:
if (isValidData(receivedData)) { // 数据有效,可以安全使用 } else { // 数据无效,可以抛出异常或进行错误处理 }
2.处理空值和异常
处理空值和异常是确保应用程序稳定性的重要部分。当你从路由接收数据时,应该始终假设这些数据可能为空或者不是预期的格式。下面是一些处理这些情况的策略:
处理空值
当你期望的数据可能为空时,可以使用Dart的null-aware运算符来优雅地处理:
String data = receivedData ?? '默认值';
或者在使用之前检查数据是否为null:
if (receivedData != null) { // 使用 receivedData } else { // 处理空值情况,例如返回错误提示或设置默认值 }
异常处理
如果数据转换或验证过程中可能抛出异常,你应该使用try-catch
语句来捕获这些异常:
try { // 尝试使用 receivedData } catch (e) { // 处理异常,例如记录日志、显示错误信息等 }
五、使用 Provider 管理跨页面的状态
Provider
是一个流行的状态管理库,它依赖于 Flutter 的 InheritedWidget
来向下传递数据。它能够让你在 widget 树中跨越多个层级来传递和修改数据,而无需手动传递回调或数据。
使用 Provider
,你可以在应用的顶层提供一个状态,然后在应用的任何其他部分访问或修改这个状态。这适用于跨多个页面传递数据,甚至是整个应用的状态管理。
详细使用参见另一文https://www.jb51.net/program/319047n8q.htm
1.使用 Provider 进行状态管理和传值
首先,你需要在 pubspec.yaml
文件中添加 provider
依赖项:
dependencies: flutter: sdk: flutter provider: ^6.1.2 # 使用适合你的版本
然后,在应用顶层(即要包裹住MaterialApp)引入 Provider
:
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; void main() { runApp( // 通过 MultiProvider 可以提供多个对象 MultiProvider( providers: [ // ChangeNotifierProvider 是 Provider 的一种,它可以响应通知 ChangeNotifierProvider(create: (context) => DataProvider()), ], child: MyApp(), ), ); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: HomePage(), ); } }
// 定义一个继承自 ChangeNotifier 的数据模型,用来传递和响应变化 class DataProvider extends ChangeNotifier { String _data = "初始数据"; String get data => _data; void setData(String newData) { _data = newData; notifyListeners(); // 当更新数据时,通知监听的 widgets 进行重建 } }
HomePage
中的按钮点击时,可以使用 Provider
来更新数据:
class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { // 使用 Provider.of 来获取最近的 DataProvider 实例 final dataProvider = Provider.of<DataProvider>(context); return Scaffold( appBar: AppBar( title: Text('首页'), ), body: Center( child: ElevatedButton( onPressed: () { // 更新数据 dataProvider.setData('更新的数据'); // 导航到详情页 Navigator.push( context, MaterialPageRoute(builder: (context) => DetailsPage()), ); }, child: Text('前往详情页并传递数据'), ), ), ); } } class DetailsPage extends StatelessWidget { @override Widget build(BuildContext context) { // 监听 DataProvider,当数据变化时重建这个 widget final data = Provider.of<DataProvider>(context).data; return Scaffold( appBar: AppBar( title: Text('详情页'), ), body: Center( // 显示从 Provider 获取的数据 child: Text(data), ), ); } }
2.完整的Provider例子
下面是一个使用Provider
进行状态管理和跨页面传值的完整示例,包括异常处理和空值检查:
首先,确保已经添加了provider
依赖。
// main.dart import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return ChangeNotifierProvider<DataModel>( create: (_) => DataModel(), child: MaterialApp( title: 'Flutter Demo', home: HomePage(), ), ); } } class DataModel extends ChangeNotifier { String _data = ''; String get data => _data; void updateData(String newData) { if (newData.isNotEmpty) { _data = newData; notifyListeners(); } else { throw Exception('Data cannot be empty'); } } } class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Home')), body: Center( child: Consumer<DataModel>( builder: (context, dataModel, child) { return ElevatedButton( onPressed: () { try { dataModel.updateData('New Data from Home'); Navigator.push( context, MaterialPageRoute(builder: (context) => DetailsPage()), ); } catch (e) { // Handle the exception ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.toString())), ); } }, child: Text('Go to Details'), ); }, ), ), ); } } class DetailsPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Details')), body: Center( child: Consumer<DataModel>( builder: (context, dataModel, child) { return Text(dataModel.data); }, ), ), ); } }
在这个例子中,DataModel
是一个简单的数据持有类,它通过Provider
允许在应用程序的其他部分访问和修改数据。HomePage
设置新的数据,并导航到DetailsPage
。DetailsPage
显示当前的数据。如果尝试设置空的数据,DataModel
将抛出一个异常,该异常在HomePage
中被捕获并显示为一个SnackBar
。
到此这篇关于Flutter 页面跳转和传值的实现的文章就介绍到这了,更多相关Flutter 页面跳转和传值内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!