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