探討一下flutter不一樣界面之間的通訊

最多見的場景是咱們在一個界面執行了某種操做,須要在另外一個界面的某個地方(例如一個變量)產生更新等效果,就須要進行界面之間的通訊數據庫


本文將介紹四種方法來完成這種事bash

  • 使用scoped_model
  • 使用InheritedWidget
  • 使用key進行訪問
  • 父widget建立state後保存供子widget訪問


一 使用scoped_model

什麼是scoped_model?app

Scoped_model是一個dart第三方庫,提供了讓您可以輕鬆地將數據模型從父Widget傳遞到它的後代的功能。此外,它還會在模型更新時從新渲染使用該模型的全部子項。,並且不管該widget是否是有狀態的均可以進行更新,再一次build
框架

實現原理less

Scoped model使用了觀察者模式,將數據模型放在父代,後代經過找到父代的model進行數據渲染,最後數據改變時將數據傳回,父代再通知全部用到了該model的子代去更新狀態。
ide

用途函數

能夠進行全局公共數據共享,除了使用scoped_model也可使用shared_preferences、或者新建一個類將共享數據設置成static類的成員。ui

  • 使用sp的缺點是效率低,須要讀寫數據庫,同時不能夠在狀態改變後主動發起更新
  • 使用static類成員的缺點也是不能夠主動發起類更新
  • 使用scoped_model的速度上更快,而且能夠監聽變換後自動發起setState變化

具體使用this

1 添加依賴spa

scoped_model: ^0.3.0複製代碼

2 、建立Model

import 'package:scoped_model/scoped_model.dart';

class CountModel extends Model{
  int _count = 0;
  get count => _count;
  
  void increment(){
    _count++;
    notifyListeners();
  }
}複製代碼

三、將model放在頂層

//建立頂層狀態
  CountModel countModel = CountModel();

  @override
  Widget build(BuildContext context) {
    return ScopedModel<CountModel>(
      model: countModel,
      child: new MaterialApp(
        home: TopScreen(),
      ),
    );
  }複製代碼

四、在子界面中獲取Model

@override
  Widget build(BuildContext context) {
    return ScopedModelDescendant<CountModel>(
      builder: (context,child,model){
        return Scaffold(
          body: Center(
            child: Text(
              model.count.toString(),
              style: TextStyle(fontSize: 48.0),
            ),
          ),
        );
      },
    );
  }複製代碼

5 一個簡單的demo能夠直接運行

import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';


class CountModel extends Model{
  int _count = 0;
  get count => _count;

  void increment()
  {
    _count++;
    notifyListeners();
  }
}

void main()
{
  CountModel countModel = CountModel();
  runApp(new ScopedModel(model: countModel, child: MaterialApp(
    home: PageA(),
  )));
}

class PageA extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return new _PageA();
  }
}

class _PageA extends State<PageA>
{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return ScopedModelDescendant<CountModel>(
      builder: (context,child,model)
      {
        return Scaffold(
          appBar: AppBar(
            title: Text(model.count.toString()),
          ),
          body: RaisedButton(
            onPressed: (){model.increment();},
          ),
        );
      },
    );
  }
}複製代碼


二 使用InhertedWidget

什麼是InheritedWidget?

InheritedWidget 是一個特殊的 Widget,它將做爲另外一個子樹的父節點放置在 Widget 樹中。該子樹的全部 widget 都必須可以與該 InheritedWidget 暴露的數據進行交互


爲了更好理解概念,咱們看一個例子

class MyInheritedWidget extends InheritedWidget {
   MyInheritedWidget({
      Key key,
      @required Widget child,
      this.data,
   }): super(key: key, child: child);

   final data;

   static MyInheritedWidget of(BuildContext context) {
      return context.inheritFromWidgetOfExactType(MyInheritedWidget);
   }

   @override
   bool updateShouldNotify(MyInheritedWidget oldWidget) => data != oldWidget.data;
}
複製代碼

以上代碼定義了一個名爲 「MyInheritedWidget」 的 Widget,目的在於爲子樹中的全部 widget 提供某些『共享』數據。

如前所述,爲了可以傳播/共享某些數據,須要將 InheritedWidget 放置在 widget 樹的頂部,這解釋了傳遞給 InheritedWidget 基礎構造函數的 @required Widget child 參數。

static MyInheritedWidget of(BuildContext context) 方法容許全部子 widget 經過包含的 context 得到最近的 MyInheritedWidget 實例(參見後面的內容)。

最後重寫 updateShouldNotify 方法用來告訴 InheritedWidget 若是對數據進行了修改,是否必須將通知傳遞給全部子 widget(已註冊/已訂閱)。

所以,使用時咱們須要把它放在父節點

class MyParentWidget... {
   ...
   @override
   Widget build(BuildContext context){
      return new MyInheritedWidget(
         data: counter,
         child: new Row(
            children: <Widget>[
               ...
            ],
         ),
      );
   }
}複製代碼

子結點如何訪問InhertedWidget中的數據呢?

class MyChildWidget... {
   ...

   @override
   Widget build(BuildContext context){
      final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context);

      ///
      /// 此刻,該 widget 可以使用 MyInheritedWidget 暴露的數據
      /// 經過調用:inheritedWidget.data
      ///
      return new Container(
         color: inheritedWidget.data.color,
      );
   }
}複製代碼

InhertedWidget的高級用法

上文講到的用法有一個明顯的缺點是沒法實現相似於Scoped_model的自動更新,爲此,咱們將inhertedWidget依託於一個statefulWidget實現更新

場景以下:



   爲了說明交互方式,咱們作如下假設:

  • ‘Widget A’ 是一個將項目添加到購物車裏的按鈕;
  • ‘Widget B’ 是一個顯示購物車中商品數量的文本;
  • ‘Widget C’ 位於 Widget B 旁邊,是一個內置任意文本的文本;
  • 咱們但願 ‘Widget A’ 在按下時 ‘Widget B’ 可以自動在購物車中顯示正確數量的項目,但咱們不但願重建 ‘Widget C’

示例代碼以下

class Item {
   String reference;

   Item(this.reference);
}

class _MyInherited extends InheritedWidget {
  _MyInherited({
    Key key,
    @required Widget child,
    @required this.data,
  }) : super(key: key, child: child);

  final MyInheritedWidgetState data;

  @override
  bool updateShouldNotify(_MyInherited oldWidget) {
    return true;
  }
}

class MyInheritedWidget extends StatefulWidget {
  MyInheritedWidget({
    Key key,
    this.child,
  }): super(key: key);

  final Widget child;

  @override
  MyInheritedWidgetState createState() => new MyInheritedWidgetState();

  static MyInheritedWidgetState of(BuildContext context){
    return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
  }
}

class MyInheritedWidgetState extends State<MyInheritedWidget>{
  /// List of Items
  List<Item> _items = <Item>[];

  /// Getter (number of items)
  int get itemsCount => _items.length;

  /// Helper method to add an Item
  void addItem(String reference){
    setState((){
      _items.add(new Item(reference));
    });
  }

  @override
  Widget build(BuildContext context){
    return new _MyInherited(
      data: this,
      child: widget.child,
    );
  }
}

class MyTree extends StatefulWidget {
  @override
  _MyTreeState createState() => new _MyTreeState();
}

class _MyTreeState extends State<MyTree> {
  @override
  Widget build(BuildContext context) {
    return new MyInheritedWidget(
      child: new Scaffold(
        appBar: new AppBar(
          title: new Text('Title'),
        ),
        body: new Column(
          children: <Widget>[
            new WidgetA(),
            new Container(
              child: new Row(
                children: <Widget>[
                  new Icon(Icons.shopping_cart),
                  new WidgetB(),
                  new WidgetC(),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidgetState state = MyInheritedWidget.of(context);
    return new Container(
      child: new RaisedButton(
        child: new Text('Add Item'),
        onPressed: () {
          state.addItem('new item');
        },
      ),
    );
  }
}

class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidgetState state = MyInheritedWidget.of(context);
    return new Text('${state.itemsCount}');
  }
}

class WidgetC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Text('I am Widget C');
  }
}
複製代碼

說明

在這個很是基本的例子中:

  • _MyInherited 是一個 InheritedWidget,每次咱們經過 ‘Widget A’ 按鈕添加一個項目時它都會從新建立
  • MyInheritedWidget 是一個 State 包含了項目列表的 Widget。能夠經過 static MyInheritedWidgetState of(BuildContext context) 訪問該 State
  • MyInheritedWidgetState 暴露了一個獲取 itemsCount 的 getter 方法 和一個 addItem 方法,以便它們能夠被 widget 使用,這是 widget 樹的一部分
  • 每次咱們將項目添加到 State 時,MyInheritedWidgetState 都會重建
  • MyTree 類僅構建了一個 widget 樹,並將 MyInheritedWidget 做爲樹的根節點
  • WidgetA 是一個簡單的 RaisedButton,當按下它時,它將從最近MyInheritedWidget 實例中調用 addItem 方法
  • WidgetB 是一個簡單的 Text,用來顯示最近 級別 MyInheritedWidget 的項目數


三 使用key進行訪問

什麼是key?

在 Fultter 中,每個 Widget 都是被惟一標識的。這個惟一標識在 build/rendering 階段由框架定義。

該惟一標識對應於可選的 Key 參數。若是省略該參數,Flutter 將會爲你生成一個。

在某些狀況下,你可能須要強制使用此 key,以即可以經過其 key 訪問 widget。

爲此,你可使用如下方法中的任何一個:GlobalKeyLocalKeyUniqueKeyObjectKey

GlobalKey 確保生成的 key 在整個應用中是惟一的。


強制使用GlobalKey的方法

GlobalKey myKey = new GlobalKey();
...
@override
Widget build(BuildContext context){
    return new MyWidget(
        key: myKey
    );
}
複製代碼


使用GlobalKey訪問State

class _MyScreenState extends State<MyScreen> {
    /// the unique identity of the Scaffold
    final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();

    @override
    Widget build(BuildContext context){
        return new Scaffold(
            key: _scaffoldKey,
            appBar: new AppBar(
                title: new Text('My Screen'),
            ),
            body: new Center(
                new RaiseButton(
                    child: new Text('Hit me'),
                    onPressed: (){
                        _scaffoldKey.currentState.showSnackBar(
                            new SnackBar(
                                content: new Text('This is the Snackbar...'),
                            )
                        );
                    }
                ),
            ),
        );
    }
}

複製代碼


四 父widget建立state後保存供子widget訪問

假設你有一個屬於另外一個 Widget 的子樹的 Widget,以下圖所示。


使用步驟以下

1. 『帶有 State 的 Widget』(紅色)須要暴露其 State

爲了暴露State,Widget 須要在建立時記錄它,以下所示:

class MyExposingWidget extends StatefulWidget {

   MyExposingWidgetState myState;

   @override
   MyExposingWidgetState createState(){
      myState = new MyExposingWidgetState();
      return myState;
   }
}
複製代碼複製代碼

2. 「Widget State」 須要暴露一些 getter/setter 方法

爲了讓「其餘類」 設置/獲取 State 中的屬性,Widget State 須要經過如下方式受權訪問:

  • 公共屬性(不推薦)
  • getter / setter

例子:

class MyExposingWidgetState extends State<MyExposingWidget>{
   Color _color;

   Color get color => _color;
   ...
}
複製代碼複製代碼

3. 「對獲取 State 感興趣的 Widget」(藍色)須要獲得 State 的引用

class MyChildWidget extends StatelessWidget {
   @override
   Widget build(BuildContext context){
      final MyExposingWidget widget = context.ancestorWidgetOfExactType(MyExposingWidget);
      final MyExposingWidgetState state = widget?.myState;

      return new Container(
         color: state == null ? Colors.blue : state.color,
      );
   }
}複製代碼
相關文章
相關標籤/搜索