Flutter 頁面間數據傳遞(共享)的幾種經常使用方式

前言

    在Android中,咱們常遇到的場景就是在頁面跳轉(Frament,Activity)時候,要將當前的部分數據攜帶到另一個頁面中,供另外頁面使用。這時候咱們經常使用的就是使用Intent, Bundle等攜帶數據。bash

    那麼在Flutter的開發過程當中,頁面之間的數據傳遞也是必不可少的,又是怎麼把一個頁面的數據傳遞(共享)給另一個頁面,或者關閉當前頁面並把當前頁面的數據帶給前一個頁面。markdown

    本篇文章將會介紹Flutter中,頁面面之間的數據傳遞(共享)的幾種常見方式及場景。app

在開始數據傳遞以前咱們先建立一個傳遞數據的類less

在Android中傳遞對象咱們須要序列化實現Serializable或者Parcelable接口才能被傳遞,在Flutter中數據傳遞沒有序列化的方法,直接就能夠傳遞對象。定義一個簡單的類以下:異步

///用來傳遞數據的實體
class TransferDataEntity {
  String name;
  String id;
  int age;

  TransferDataEntity(this.name, this.id, this.age);
}
複製代碼

咱們具體看看數據傳遞的方式async

經過構造器(constructor)傳遞數據

    經過構造器傳遞數據是一種最簡單的方式,也是最經常使用的方式,在第一個頁面,咱們模擬建立一個咱們須要傳遞數據的對象。當點擊跳轉的時候,咱們把數據傳遞給DataTransferByConstructorPage頁面,並把攜帶過來的數據展現到頁面上。ide

  • 建立一個傳遞數據對象post

    final data = TransferDataEntity("001", "張三丰", 18);
    複製代碼
  • 定義一個跳轉到DataTransferByConstructorPage頁面的方法ui

    _transferDataByConstructor(BuildContext context, TransferDataEntity data) {
        Navigator.push(
            context,
            MaterialPageRoute(
                builder: (context) => DataTransferByConstructorPage(data: data)));
      }
    複製代碼
  • 在DataTransferByConstructorPage頁面接收到數據並展現出來,代碼以下this

    咱們只須要作兩件事:

    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中經常使用的幾種頁面之間傳遞數據的方式,其中最後一種方式提到了StreamStreamBuilder我有一篇文章專門介紹了FlutterStreamFlutter Stream簡介及使用 詳細的介紹了Stream及部分操做的使用。

    如今官方推薦的provider實際上就是使用了InheritedWidget有時間的話建議詳細了下InheritedWidget及使用方法。

    以上是對頁面之間值傳遞的一個總結,本文Demo,若有寫的不足之處,望指正~

相關文章
相關標籤/搜索