一、页面跳转
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
。