前言
在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,如有写的不足之处,望指正~