日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Flutter?狀態管理scoped?model源碼解讀_Android

作者:feelingHy ? 更新時間: 2022-12-10 編程語言

一、什么是 scoped_model

本文主要從 scoped_model 的簡單使用說起,然后再深入源碼進行剖析(InheritedWidget、Listenable、AnimatedBuilder),不會探討 Flutter 狀態管理的優劣,單純為了學習作者的設計思想。

scoped_model 是一個第三方 Dart 庫,可以讓您輕松的將數據模型從父 Widget 傳遞到子 Widget。此外,它還會在模型更新時重新構建所有使用該模型的子 Widget。

它直接來自于 Google 正在開發的新系統 Fuchsia 核心 Widgets 中對 Model 類的簡單提取,作為獨立使用的獨立 Flutter 插件發布。

二、用法

class CounterModel extends Model {
  int _counter = 0;
  int get counter => _counter;
  static CounterModel of(BuildContext context) =>
      ScopedModel.of<CounterModel>(context, rebuildOnChange: true);
  void increment() {
    _counter++;
    notifyListeners();
  }
}
class ScopedModelDemoPage extends StatelessWidget {
  const ScopedModelDemoPage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('scopedModel'),
        centerTitle: true,
      ),
      body: ScopedModel(
        model: CounterModel(),
        child: ScopedModelDescendant<CounterModel>(
          builder: (context, child, model) => Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('${model.counter}'),
                OutlinedButton(
                  onPressed: ScopedModel.of<CounterModel>(context).increment,
                  // onPressed: model.increment,
                  child: Text('add'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

從上面的代碼可以看出 scoped_model 的使用非常簡單,只需要以下三步:

  • 定義 Model 的實現,如 CounterModel,這里需要注意的是 CounterModel 一定要繼承自 Model(為什么一定要繼承 Model 我們后面細說)并且在狀態改變的時候執行 notifyListeners。
  • 使用 ScopedModel Widget 包裹需要用到 Model 的 Widget。
  • 使用 ScopedModelDescendant 或者 ScopedModel.of<CounterModel>(context) 來進行獲取數據。

三、實現原理

在 scoped_model 中的整個實現中,它很巧妙的借助了 AnimatedBuilder、Listenable、InheritedWidget 等 Flutter 的基礎特性。

scoped_model 使用了觀察者模式,將數據放在父 Widget,子 Widget 通過找到父 Widget 的 model 進行數據渲染,最后改變數據的時候再將數據傳回,父 Widget 再通知所有用到了該 model 的子 Widget 去更新狀態。

我們首先從 ScopedModel 入手,通過源碼我們不難發現,ScopedModel 是一個 StatelessWidget 最后返回一個 AnimatedBuilder,在 AnimatedBuilder 中在通過 builder 返回 _InheritedModel

我們再從 Model 入手,可以看出 Model 是一個 繼承自 Listenable 的抽象類,主要有一個 _listeners 變量用 Set 來進行存儲,復寫了 addListenerremoveListenernotifyListeners 方法。在這里不知道大家有沒有想過 Model 為什么要繼承 Listenable? 在這里先賣個關子,在后面會詳細講解。

如果只是單單看 ScopedModelModel 好像也看不出來什么巧妙之處,但是如果把 ScopedModel 中返回的 AnimatedBuilderModel 所繼承的 Listenable 結合起來進行思考就會發現,AnimatedBuilder 繼承自 AnimatedWidget,在 AnimatedWidget 的生命周期中會對 Listenable 添加監聽,而 Model 正好就實現了 Listenable 接口。

Model 實現了 Listenable 接口,內部剛好有一個 Set<VoidCallback> _listeners 用來保存接收者。當 Model 賦值給 AnimatedBuilder 中的 animation 時,Listenable 的 addListener 就會被調用,然后添加一個 _handleChange 方法,_handleChange 內部只有一行代碼 setState((){}),當調用 notifyListeners 時,會從創建一個 Microtask,去執行一遍 _listeners 中的 _handleChange ,當 _handleChange 被調用時就會進行更新 UI 界面。其實這里也就解釋了 Model 為什么要繼承 Listenable

不知道大家發現沒有,講了這么多,還沒有講到 ScopedModelDescendant 到底是干什么的?那我就不得不先說起 InheritedWidget 了。

InheritedWidget 是 Flutter 中非常重要的一個功能型組件,它提供了一種在 Widget 樹中從上到下共享數據的方式,比如我們在應用的根 Widget 中通過 InheritedWidget 共享了一個數據,那么我們便可以在任意子 Widget 中來獲取該共享的數據。它主要有以下兩個作用:

  • 子 Widget 可以通過 Inherited Widgets 提供的靜態 of 方法拿到離他最近的父Inherited widgets 實例。
  • Inherited Widgets 改變 state 之后,會自動觸發 state 消費者的 rebuild 行為。

scoped_model 中我們可以通過 ScopedModel.of<CountModel>(context) 來獲取 我們的 model,最主要的就是在 ScopedModel 中返回了 AnimatedBuilder,而 AnimatedBuilder 中 builder 又返回了 _InheritedModel_InheritedModel 又繼承了 InheritedWidget

言歸正傳,我們一起回到 ScopedModelDescendant 的主題,不知道大家有沒有嘗試過,不用 ScopedModelDescendant 來獲取 model 會發生什么樣的情況?通過窺探源碼我們發現有 ScopedModelError 這樣一個異常類,說的已經很明確了,必須要提供 ScopedModelDescendant。what ?其實它主要做了以下 2 件事情:

  • 隱式調用 ScopedModel.of<T>(context) 來獲取 model。
  • 明確語義化,不然我們每次都需要用 Builder 來進行構建,不然將獲取不到 model,還會拋出異常。

// 不使用 ScopedModelDescendant 使用 Builder 的用法
class ScopedModelDemoPage extends StatelessWidget {
  const ScopedModelDemoPage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('scopedModel'),
        centerTitle: true,
      ),
      body: ScopedModel(
        model: CounterModel(),
        child: Builder(
          builder: (context) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('${CounterModel.of(context).counter}'),
                OutlinedButton(
                  onPressed: CounterModel.of(context).increment,
                  child: Text('add1'),
                ),
              ],
            );
          },
        ),
        // child: ScopedModelDescendant<CounterModel>(
        //   builder: (context, child, model) => Center(
        //     child: Column(
        //       mainAxisAlignment: MainAxisAlignment.center,
        //       children: [
        //         Text('${model.counter}'),
        //         OutlinedButton(
        //           onPressed: ScopedModel.of<CounterModel>(context).increment,
        //           // onPressed: model.increment,
        //           child: Text('add'),
        //         ),
        //       ],
        //     ),
        //   ),
        // ),
      ),
    );
  }
}

除了上面這種使用 Builder 的方式,當然我們還可以使用下面的方法,把它單獨提取出一個 Widget,代碼如下:

// 單獨提取 Widget 的方式
class ScopedModelDemoPage extends StatelessWidget {
  const ScopedModelDemoPage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('scopedModel'),
        centerTitle: true,
      ),
      body: ScopedModel<CounterModel>(
        model: CounterModel(),
        // 不使用 ScopedModelDescendant 的用法
        // child: Builder(
        //   builder: (context) {
        //     return Column(
        //       mainAxisAlignment: MainAxisAlignment.center,
        //       children: [
        //         Text('${CounterModel.of(context).counter}'),
        //         OutlinedButton(
        //           onPressed: CounterModel.of(context).increment,
        //           child: Text('add1'),
        //         ),
        //       ],
        //     );
        //   },
        // ),
        child: NewWidget(),
      ),
    );
  }
}
class NewWidget extends StatelessWidget {
  const NewWidget({
    Key? key,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('${CounterModel.of(context).counter}'),
          OutlinedButton(
            onPressed: CounterModel.of(context).increment,
            // onPressed: model.increment,
            child: Text('add'),
          ),
        ],
      ),
    );
  }
}
class CounterModel extends Model {
  int _counter = 0;
  int get counter => _counter;
  static CounterModel of(BuildContext context) =>
      ScopedModel.of<CounterModel>(context, rebuildOnChange: true);
  void increment() {
    _counter++;
    notifyListeners();
  }
}

是不是很意外?主要起作用的是下面這一段代碼:

單獨提取出來 Widget,可以獲取到正確的 context,從而可以獲取到離他最近的父 Inherited?widgets 實例。

四、結束

以上就是我對 scoped_model 的使用以及部分源碼的解讀,如果有不足之處,還請指教。 最后,大家也可以思考一下,我們是如何通過 context 就能獲取到共享的 Model 呢?

原文鏈接:https://juejin.cn/post/7163838946219130894

欄目分類
最近更新