共计 9256 个字符,预计需要花费 24 分钟才能阅读完成。
前言
在 Android 中,我们常遇到的场景就是在页面跳转 (Frament,Activity) 时候,要将当前的部分数据携带到另外一个页面中,供另外页面使用。这时候我们常用的就是使用 Intent,Bundle 等携带数据。
那么在 Flutter 的开发过程中,页面之间的数据传递也是必不可少的,又是怎么把一个页面的数据传递 (共享) 给另外一个页面,或者关闭当前页面并把当前页面的数据带给前一个页面。
本篇文章将会介绍 Flutter 中,页面面之间的数据传递 (共享) 的几种常见方式及场景。
在开始数据传递之前我们先创建一个传递数据的类
在 Android 中传递对象我们需要序列化实现
Serializable
或者Parcelable
接口才能被传递,在 Flutter 中数据传递没有序列化的方法,直接就可以传递对象。定义一个简单的类如下:
/// 用来传递数据的实体
class TransferDataEntity {
String name;
String id;
int age;
TransferDataEntity(this.name, this.id, this.age);
}
我们具体看看数据传递的方式
通过构造器 (constructor) 传递数据
通过构造器传递数据是一种最简单的方式,也是最常用的方式,在第一个页面,我们模拟创建一个我们需要传递数据的对象。当点击跳转的时候,我们把数据传递给 DataTransferByConstructorPage 页面,并把携带过来的数据展示到页面上。
-
创建一个传递数据对象
final data = TransferDataEntity("001", "张三丰", 18);
-
定义一个跳转到 DataTransferByConstructorPage 页面的方法
_transferDataByConstructor(BuildContext context, TransferDataEntity data) { Navigator.push( context, MaterialPageRoute(builder: (context) => DataTransferByConstructorPage(data: data))); }
-
在 DataTransferByConstructorPage 页面接收到数据并展示出来,代码如下
我们只需要做两件事:
1. 提供一个 final 变量
final TransferDataEntity data
2. 提供一个构造器接收参数
DataTransferByConstructorPage({this.data});
/// 通过构造器的方式传递参数 class DataTransferByConstructorPage extends StatelessWidget { final TransferDataEntity data; DataTransferByConstructorPage({this.data}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("构造器方式"), ), body: Column( children: <Widget>[ Container( width: double.infinity, height: 40.0, alignment: Alignment.center, child: Text(data.id), ), Container( width: double.infinity, height: 40.0, alignment: Alignment.center, child: Text(data.name), ), Container( width: double.infinity, height: 40.0, alignment: Alignment.center, child: Text("${data.age}"), ) ], ), ); } }
当一个页面关闭时携带数据到上一个页面(Navigator.pop)
在 Android 开发中我们需要将数据传递给上一个页面通常使用的传统方式是 startActivityForResult()方法。但是在 flutter 就不用这么麻烦了。只需要使用 Navigator.pop 方法即可将数据结果带回去。但是我们跳转的时候需要注意两点:
1. 我们需要定义一个异步方法用于接收返回来的结果
/// 跳转的时候我们需要使用异步等待回调结果 dataFromOtherPage 就是返回的结果
_toTransferForResult(BuildContext context, TransferDataEntity data) async {
final dataFromOtherPage = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => TransferRouterPage(data: data)),
) as TransferDataEntity;
}
2. 在我们要关闭的页面使用 Navigator.pop 返回第一个页面
// 返回并携带数据
_backToData(BuildContext context){var transferData = TransferDataEntity("嘻嘻哈哈","007",20);
Navigator.pop(context,transferData);
}
InheritedWidget 方式
官网给出的解释:InheritedWidget 是 Flutter 中非常重要的一个功能型 Widget,它可以高效的将数据在 Widget 树中向下传递、共享,这在一些需要在 Widget 树中共享数据的场景中非常方便,如 Flutter 中,正是通过 InheritedWidget 来共享应用主题 (Theme) 和 Locale(当前语言环境)信息的。
InheritedWidget 和 React 中的 context 功能类似,和逐级传递数据相比,它们能实现组件跨级传递数据。InheritedWidget 的在 Widget 树中数据传递方向是从上到下的,这和 Notification 的传递方向正好相反。
优点:可以控制每个 Widget 单独去取数据并使用
如果一个页面只存在同一层级的 Weiget 这时候使用构造器的方式当然是最简单的,也是最方便的,但是如果一个页面存在多个层级的 Weiget 这时候构造器的方法就有了局限性,这时候我们使用 InheritedWidget 是一个比较好的选择。
使用 InheritedWidget
方式如下几步:
-
继承 InheritedWidget 提供一个数据源
class IDataProvider extends InheritedWidget{ final TransferDataEntity data; IDataProvider({Widget child,this.data}):super(child:child); @override bool updateShouldNotify(IDataProvider oldWidget) {return data!=oldWidget.data;} static IDataProvider of(BuildContext context){return context.inheritFromWidgetOfExactType(IDataProvider); } }
-
定义页面跳转时候携带数据的方法
/// 跳转到 IDataWidget 页面并携带数据 _inheritedToPage(BuildContext context, TransferDataEntity data) { Navigator.push( context, MaterialPageRoute(builder: (context) => IDataProvider(child: IDataWidget(), data: data, ))); }
-
跳转的到的页面并展示数据代码如下
class IDataWidget extends StatelessWidget { @override Widget build(BuildContext context) {final data = IDataProvider.of(context).data; return Scaffold( appBar: AppBar(title: Text("Inherited 方式传递数据"), ), body: Column( children: <Widget>[ Container( alignment: Alignment.center, height: 40.0, child: Text(data.name), ), Container( alignment: Alignment.center, height: 40.0, child: Text(data.id), ), Container( alignment: Alignment.center, height: 40.0, child: Text("${data.age}"), ), IDataChildWidget()], ), ); } } class IDataChildWidget extends StatelessWidget { @override Widget build(BuildContext context) {final data = IDataProvider.of(context).data; return Container(child: Text(data.name), ); } }
我们将上面的
IDataProvier
进行改造加入泛型就可以通用了
1. 修改后的 Provider 类如下
class IGenericDataProvider<T> extends InheritedWidget {
final T data;
IGenericDataProvider({Key key, Widget child, this.data})
: super(key: key, child: child);
@override
bool updateShouldNotify(IGenericDataProvider oldWidget) {return data != oldWidget.data;}
static T of<T>(BuildContext context) {
return (context.inheritFromWidgetOfExactType(IGenericDataProvider<T>().runtimeType) as IGenericDataProvider<T>).data;
}
}
2. 使用跳转的时候修改代码如下(主要是添加泛型支持)
_inheritedGenericToPage(BuildContext context, TransferDataEntity data) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => IGenericDataProvider<TransferDataEntity>(child: IDataWidget(),
data: data,
)));
}
接收传递的值的方式如下
IGenericDataProvider.of<TransferDataEntity>(context) 可以直接取值
class IGenericDataWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {final data = IGenericDataProvider.of<TransferDataEntity>(context);
return Scaffold(
appBar: AppBar(title: Text("Inherited 泛型方式传递数据"),
),
body: Column(
children: <Widget>[
Container(
alignment: Alignment.center,
height: 40.0,
child: Text(data.name),
),
Container(
alignment: Alignment.center,
height: 40.0,
child: Text(data.id),
),
Container(
alignment: Alignment.center,
height: 40.0,
child: Text("${data.age}"),
),
],
),
);
}
}
全局的提供数据的方式
这种方式我们还是使用 InheritedWidget,区别就是我们不是跳转的时候去创建 IGenericDataProvider。而是把他放在最顶层
注意:这种方式一定要把数据放在顶层
定义顶部数据
class MyApp extends StatelessWidget {
// This widget is the root of your application.
// 传递值的数据
var params = InheritedParams();
@override
Widget build(BuildContext context) {
return IGenericDataProvider(
data: params,
child: MaterialApp(
title: 'Data Transfer Demo',
theme: ThemeData(primarySwatch: Colors.blue,),
home: MyHomePage(title: 'Data Transfer Demo'),
),
);
}
}
接收数据的方式基本和
InheritedWidget
相同final data = IGenericDataProvider.of<TransferDataEntity>(context),获取数据
class InheritedParamsPage extends StatefulWidget {
@override
_InheritedParamsPageState createState() => _InheritedParamsPageState();
}
class _InheritedParamsPageState extends State<InheritedParamsPage> {
@override
Widget build(BuildContext context) {final data = IGenericDataProvider.of<TransferDataEntity>(context);
return Scaffold(
appBar: AppBar(title: Text("通过全局数据方式"),
),
body:Column(
children: <Widget>[
Container(
alignment: Alignment.center,
height: 40.0,
child: Text(data.name),
),
Container(
alignment: Alignment.center,
height: 40.0,
child: Text(data.id),
),
Container(
alignment: Alignment.center,
height: 40.0,
child: Text("${data.age}"),
),
],
),
);
}
}
通过全局单例模式来使用
这种方式就是创建一个全局单例对象,任何地方都可以操控这个对象,存储和取值都可以通过这个对象
- 创建单例对象
class TransferDataSingleton {
static final TransferDataSingleton _instanceTransfer =
TransferDataSingleton.__internal();
TransferDataEntity transData;
factory TransferDataSingleton() {return _instanceTransfer;}
TransferDataSingleton.__internal();}
final transSingletonData = TransferDataSingleton();
- 给单例对象存放数据
_singletonDataTransfer(BuildContext context) {var transferData = TransferDataEntity("二汪", "002", 25);
transSingletonData.transData = transferData;
Navigator.push(context,
MaterialPageRoute(builder: (context) => TransferSingletonPage()));
}
- 接收并使用传递的值
class TransferSingletonPage extends StatefulWidget {
@override
_TransferSingletonPageState createState() => _TransferSingletonPageState();
}
class _TransferSingletonPageState extends State<TransferSingletonPage> {
@override
Widget build(BuildContext context) {
// 直接引入单例对象使用
var data = transSingletonData.transData;
return Scaffold(
appBar: AppBar(title: Text("全局单例传递数据"),
),
body: Column(
children: <Widget>[
Container(
alignment: Alignment.center,
height: 40.0,
child: Text(data.name),
),
Container(
alignment: Alignment.center,
height: 40.0,
child: Text(data.id),
),
Container(
alignment: Alignment.center,
height: 40.0,
child: Text("${data.age}"),
),
],
),
);
}
}
全局单例结合 Stream 的方式传递数据
- 创建一个接受全局的单例对象,并把传递值转成 Stream 方式
- 在接收数据可以使用 StreamBuilder 直接接收并处理
class TransferStreamSingleton {
static final TransferStreamSingleton _instanceTransfer =
TransferStreamSingleton.__internal();
StreamController streamController;
void setTransferData(TransferDataEntity transData) {streamController = StreamController<TransferDataEntity>();
streamController.sink.add(transData);
}
factory TransferStreamSingleton() {return _instanceTransfer;}
TransferStreamSingleton.__internal();}
final streamSingletonData = TransferStreamSingleton();
- 传递要携带的数据
_streamDataTransfer(BuildContext context) {var transferData = TransferDataEntity("三喵", "005", 20);
streamSingletonData.setTransferData(transferData);
Navigator.push(context,
MaterialPageRoute(builder: (context) => TransferStreamPage()));
}
- 接收要传递的值
class TransferStreamPage extends StatefulWidget {
@override
_TransferStreamPageState createState() => _TransferStreamPageState();
}
class _TransferStreamPageState extends State<TransferStreamPage> {
StreamController _streamController = streamSingletonData.streamController;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("全局单例结合 Stream"),
),
body: StreamBuilder(
stream: _streamController.stream,
initialData: TransferDataEntity("","", 0),
builder: (context, snapshot) {
return Column(
children: <Widget>[
Container(
alignment: Alignment.center,
height: 40.0,
child: Text(snapshot.data.name),
),
Container(
alignment: Alignment.center,
height: 40.0,
child: Text(snapshot.data.id),
),
Container(
alignment: Alignment.center,
height: 40.0,
child: Text("${snapshot.data.age}"),
),
],
);
}));
}
@override
void dispose() {_streamController.close();
super.dispose();}
}
总结
以上是我们在在 Flutter 中常用的几种页面之间传递数据的方式,其中最后一种方式提到了
Stream
和StreamBuilder
我有一篇文章专门介绍了Flutter
的Stream
。Flutter Stream 简介及使用 详细的介绍了Stream
及部分操作的使用。现在官方推荐的 provider 实际上就是使用了
InheritedWidget
有时间的话建议详细了下InheritedWidget
及使用方法。以上是对页面之间值传递的一个总结,本文 Demo,如有写的不足之处,望指正~