一、页面跳转
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 {@overrideWidget 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 {@overrideWidget 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 {@overrideWidget 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 {@overrideWidget 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 {@overrideWidget 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 {@overrideWidget 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});@overrideWidget 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 {@overrideWidget 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 {@overrideWidget 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 {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('New Page'),),body: PopScope(canPop: false, // 使用canPop提前禁用poponPopInvoked: (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 {@overrideWidget build(BuildContext context) {return MaterialApp(// 应用初始路由initialRoute: '/',// onGenerateRoute 用于处理动态路由onGenerateRoute: (RouteSettings settings) {// 获取传递过来的参数,如果参数为null,则提供一个空的Mapfinal 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});@overrideState<HomePage> createState() => _HomePageState();
}class _HomePageState extends State<HomePage> {String _data = "缺省值";@overrideWidget 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});@overrideWidget 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 {@overrideWidget 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://gamin.blog.csdn.net/article/details/136556092
1.使用 Provider 进行状态管理和传值
首先,你需要在 pubspec.yaml
文件中添加 provider
依赖项:
dependencies:flutter:sdk: flutterprovider: ^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 {@overrideWidget 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 {@overrideWidget 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 {@overrideWidget build(BuildContext context) {// 监听 DataProvider,当数据变化时重建这个 widgetfinal 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 {@overrideWidget 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 {@overrideWidget 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 exceptionScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())),);}},child: Text('Go to Details'),);},),),);}
}class DetailsPage extends StatelessWidget {@overrideWidget 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
。