Flutter 状态管理scoped model源码解读

 更新时间:2022年11月11日 11:23:21   作者:feelingHy  
这篇文章主要为大家介绍了Flutter 状态管理scoped model源码解读,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

一、什么是 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 呢?

以上就是Flutter 状态管理scoped model源码解读的详细内容,更多关于Flutter状态管理 scoped model的资料请关注脚本之家其它相关文章!

相关文章

  • Android自定义控件仿ios下拉回弹效果

    Android自定义控件仿ios下拉回弹效果

    这篇文章主要为大家详细介绍了Android自定义控件仿ios下拉回弹效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-01-01
  • Android 保存文件路径方法

    Android 保存文件路径方法

    今天小编就为大家分享一篇Android 保存文件路径方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-08-08
  • Android 匿名内存深入分析

    Android 匿名内存深入分析

    这篇文章主要为大家介绍了Android 匿名内存深入分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • Android实战教程第四十篇之Chronometer实现倒计时

    Android实战教程第四十篇之Chronometer实现倒计时

    这篇文章主要介绍了Android实战教程第四十篇之Chronometer实现倒计时,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-11-11
  • Android解决View的滑动冲突的方法

    Android解决View的滑动冲突的方法

    这篇文章主要介绍了Android解决View的滑动冲突的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-07-07
  • Android工具类整合教程

    Android工具类整合教程

    这篇文章主要介绍了Android工具类整合教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2020-09-09
  • Android之自定义实现BaseAdapter(通用适配器三)

    Android之自定义实现BaseAdapter(通用适配器三)

    这篇文章主要为大家详细介绍了Android之自定义实现BaseAdapter通用适配器第三篇,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-12-12
  • Android 详解沉浸式状态栏的实现流程

    Android 详解沉浸式状态栏的实现流程

    沉浸式就是要给用户提供完全沉浸的体验,使用户有一种置身于虚拟世界之中的感觉。沉浸式模式就是整个屏幕中显示都是应用的内容,没有状态栏也没有导航栏,用户不会被一些系统的界面元素所打扰,让我们来实现下网上传的沸沸扬扬的安卓沉浸式状态栏
    2021-11-11
  • Android开发登陆案例

    Android开发登陆案例

    这篇文章主要介绍了Android开发登陆案例的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-07-07
  • Android SRT字幕文件基础操作讲解

    Android SRT字幕文件基础操作讲解

    这篇文章主要介绍了Android SRT字幕文件基础操作,SRT的数据格式是通过以上单个数据节点可以提供一个大致的思路是:先定位一个数据节点的固定格式,然后将一行一行的读取到数据
    2023-01-01

最新评论