Flutter 學習之旅(四十三) Flutter 狀態 scoped_model 學習

scoped_model

scoped_model 是Flutter 中最簡單的狀態框架 ,他主要使用了InheritedWidget 來共享狀態,Microtask(微隊列)來做的異步, 寫的可以說是非常的小而精

想要看懂scoped_model 我們先來復習一下使用 InheritedWidget 的流程, 我們簡單的將共享數據的widget 命名為Provider
,消費者命名為Consumer ,方便我們接下來的理解

  1. 創建數據模型 model 繼承自 Listenable ,這個就是共享的數據 重寫他的 addListener removeListener 和 notifyListeners()
    我們平時可以使用 ChangeNotifier ,他實現了帶有 callback 的 addListener removeListener 和 notifyListeners()

  2. 創建一個數據的提供者 Provider ,他包含需要共享的數據 data,并會在被加載和移除時添加和刪除監聽,所以他是一個StatefulWidget,我們需要重寫他的initState() dispose() 和 didUpdateWidget() 這三個方法,同時他還含有一個InheritedWidget 作為child,來為他提供一個向下傳遞數據的能力,

3.Provider 是提供了一個向下共享數據的能力, 我還需要一個查找著,他擁有著可以關聯子widget和 Provider 的關系(并非強制 ,提供是否關聯參數),我們這里寫一下通用的寫法

  static T of<T extends ChangeNotifier>(BuildContext buildContext,{bool rebuild=true}){
    if(rebuild){
      return buildContext.dependOnInheritedWidgetOfExactType<InheritedProvider<T>>().data;
    }
    var p= buildContext.getElementForInheritedWidgetOfExactType<InheritedProvider<T>>().widget  as InheritedProvider<T>;
    return p.data;
  }

buildContext.getElementForInheritedWidgetOfExactType 看到了這個方法有沒有想起來在Element 那篇文章中,我們提到過的InheritedWidget 是如何共享數據的,沒錯 Widget 是通過 持有element 在 element 的 mount 的方法中 加載父widget 的 InheritedWidget , 所以這個方法名是以 ElementFor 開頭的

4.數據的持有者 提供者都有了,其實在這里我們利用 上面3 的靜態方法其實在子widget中就可以獲取到數據了,但是如果們繼續封裝接受者的話,可以方便使用者調用 ,使用者就是一個普通的StatelessWidget ,我們在他內部創建一個builder
final Widget Function(BuildContext buildContext,T data) builder; 在StatelessWidget 的build 方法直接調用builder 就可以了,省去了每次都要寫獲取數據的步驟 即 ChangeNotifyProvider.of(context,useData: true)

整個 InheritedWidget 的使用過程就已經完成了,接下來我們按照這個順序去看scoped_model 的源碼,就方便多了,每一步都會和源碼中一一對應上,我們就按照上面的步驟來分析

我會刪除部分代碼,讓大家看的更簡潔,

1.創建數據模型

abstract class Model extends Listenable {
  final Set<VoidCallback> _listeners = Set<VoidCallback>();
  int _version = 0;
  int _microtaskVersion = 0;
  @override
  void addListener(VoidCallback listener) {
    _listeners.add(listener);
  }
  @override
  void removeListener(VoidCallback listener) {
    _listeners.remove(listener);
  }
  @protected
  void notifyListeners() {
  ...
    if (_microtaskVersion == _version) {
      _microtaskVersion++;
      scheduleMicrotask(() {
        _version++;
        _microtaskVersion = _version;
        _listeners.toList().forEach((VoidCallback listener) => listener());
      });
    }
  }
}

2.創建提供者 一個StatefulWidget 包含 InheritedWidget ,

class ScopedModel<T extends Model> extends StatelessWidget {
  final T model;
  final Widget child;
  ScopedModel({@required this.model, @required this.child})
      : assert(model != null),
        assert(child != null);
  @override
  Widget build(BuildContext context) {
    return ModelBuilder<T>(
      model: model,
      builder: (context) => _InheritedModel<T>(model: model, child: child),
    );
  }

class ModelBuilder<T extends Model> extends StatefulWidget {
  final T model;
  final WidgetBuilder builder;
  const ModelBuilder({this.model, this.builder});
  @override
  _ModelBuilderState createState() => new _ModelBuilderState();
}
class _ModelBuilderState extends State<ModelBuilder> {
  @override
  void initState() {
    super.initState();
    widget.model.addListener(_onChange);
  }
  @override
  void didUpdateWidget(ModelBuilder oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.model != oldWidget.model) {
      oldWidget.model.removeListener(_onChange);
      widget.model.addListener(_onChange);
    }
  }
  @override
  void dispose() {
    widget.model.removeListener(_onChange);
    super.dispose();
  }
  @override
  Widget build(BuildContext context) => widget.builder(context);
  void _onChange() => setState(() {});
}

class _InheritedModel<T extends Model> extends InheritedWidget {
  final T model;
  final int version;

  _InheritedModel({Key key, Widget child, T model})
      : this.model = model,
        this.version = model._version,
        super(key: key, child: child);

  @override
  bool updateShouldNotify(_InheritedModel<T> oldWidget) =>
      (oldWidget.version != version);
}

這里他又使用了一個StatelessWidget 封裝了一下我們所說的步驟,方便閱讀和理解

3.創建數據查找著

class ModelFinder<T extends Model> {
  T of(BuildContext context, {bool rebuildOnChange: false}) {
    return ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange);
  }
}

  static T of<T extends Model>(
    BuildContext context, {
    bool rebuildOnChange = false,
  }) {
    final Type type = _type<_InheritedModel<T>>();

    Widget widget = rebuildOnChange
        ? context.inheritFromWidgetOfExactType(type)
        : context.ancestorWidgetOfExactType(type);

    if (widget == null) {
      throw new ScopedModelError();
    } else {
      return (widget as _InheritedModel<T>).model;
    }
  }

  static Type _type<T>() => T;

4.創建消費者

class ScopedModelDescendant<T extends Model> extends StatelessWidget {
  final ScopedModelDescendantBuilder<T> builder;
  final Widget child;
  final bool rebuildOnChange;
  ScopedModelDescendant({
    @required this.builder,
    this.child,
    this.rebuildOnChange = true,
  });
  @override
  Widget build(BuildContext context) {
    return builder(
      context,
      child,
      ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange),
    );
  }
}

具體的使用方法和InheritedWidget 一致, 提供者包裹業務界面,子widget 直接提供數據模型獲取數據,

仔細看一下代碼,其實并沒有什么難度,希望對大家的學習有所幫助

我學習flutter的整個過程都記錄在里面了
http://www.lxweimin.com/c/36554cb4c804

最后附上demo 地址

https://github.com/tsm19911014/tsm_flutter

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374