前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter 知识集锦 | 监听与通知 ChangeNotifier

Flutter 知识集锦 | 监听与通知 ChangeNotifier

作者头像
张风捷特烈
发布2023-10-19 18:53:48
1.3K0
发布2023-10-19 18:53:48
举报
文章被收录于专栏:Android知识点总结
1. 数据的提供者与消费者

今天想要和大家好好聊聊 ChangeNotifier 这个东西,从名字上来看它由 change(改变)Notifier(通知器) 构成。打个比方:

有三个铁粉跟我说: "你发新文章的时候跟我说一声"。 之后我发布文章后,分享给了他们三个人。

很明显,这是一个 发布-订阅 模式,其中:

发布者是博主,是数据的提供者,也是通知事件的执行人。 订阅者是粉丝,是数据的消费者,需要依赖数据完成需求。

所以 ChangeNotifier 的角色很明显,他的职责是:在数据变化时,触发通知的动作。在整个过程中,发布者和订阅者是一对多的关系。所以对于通知器来说,需要维护一个列表通知订阅者。


在实际开发中,有很多类似的场景。比如不同界面中有若干个组件期望得到下载的进度数据,来完成自身的视觉表现。这里 下载进度 就是核心的数据,组件 相当于订阅者,需要感知数据的变化,完成展示需求。而发布者就是 下载进度数据的 提供者

案例演示

监听-通知关系


2. 通过一个小案例了解 ChangeNotifier 的使用

下面,我们来完成上面下载进度的模拟案例,演示一下 ChangeNotifier 的使用。首先来分析一下: 在视图方面,主页面中有一个圆形的进度条 HomeProgressView 组件;点击头部栏左上角进入详情页,其中有一个矩形的进度条 DetailProgressView 组件。 在视图方面,主界面右下角按钮点击时,进度数据将会不断增加,直到 1 ;两个进度相关的组件,需要感知进度数值的变化,从而更新进度呈现。

这里只给出核心的代码,案例的完全代码已集成到 FlutterUnit,可以在仓库中自己查看 change_notifier_01~


  • 数据方面处理

由于 ChangeNotifier 是一个混入类,无法直接实例化,使用时需要被混入来发挥效力。如下所示,这里定义 ProgressValueNotifier 类混入 ChangeNotifier。进度数据是一个 double 类型的浮点数,维护在 ProgressValueNotifier 中。

数据变化的时机就是 _value 改变时,在 set 方法中更新 _value 的值,并通过 notifyListeners 方法通知监听者数据已经变化,从而让订阅者们可以感知变化,并做出响应。

代码语言:javascript
复制
ProgressValueNotifier progress = ProgressValueNotifier(); /// tag1

class ProgressValueNotifier with ChangeNotifier{

  double _value = 0;

  double get value =>_value;

  String get valueStr => '${(value*100).toStringAsFixed(1)}%';

  set value(double value){
    _value = value.clamp(0, 1);
    notifyListeners();
  }
}

注 - tag1 : 这里为了方便访问 ProgressValueNotifier 对象,先将 progress 对象作为全局变量。后续文章会继续探讨对该对象的维护方式。


这里通过 Timer.periodic 开启一个 200 ms 的周期回调,触发 _updateProgress 方法。回调方法中,每次触发增加 1% 的进度,以此模拟下载进度数值的增加。

代码语言:javascript
复制
---->[page/home/home_page.dart]----
Timer? _timer;

void _startTimer(){
  if(_timer!=null) return;
  if(progress.value==1.0){
    progress.value=0;
  }
  _timer = Timer.periodic(const Duration(milliseconds: 200),_updateProgress);
}

void _updateProgress(Timer timer) {
  if(progress.value>=1.0){
    timer.cancel();
    _timer = null;
    return;
  }
  progress.value += 0.01;
}

  • 视图方面处理

详情页的进度和主页的进度视图上的处理是基本类似的,这里只拿 DetailProgressView 来说明一下。如下代码所示,主要需要处理三个部分:

[1]. 通过 ChangeNotifier 对象的 addListener 方法添加订阅关系。 [2]. 被加入回调的函数,将会在发布通知时触发。其中可以处理 更新逻辑。 [3]. 在状态类销毁后,要及时移除监听。否则仍会在销毁后,触发更新,导致异常。

代码语言:javascript
复制
class DetailProgressView extends StatefulWidget {

  const DetailProgressView({super.key});

  @override
  State<DetailProgressView> createState() => _DetailProgressViewState();
}

class _DetailProgressViewState extends State<DetailProgressView> {

  @override
  void initState() {
    super.initState();
    /// 1. 添加监听 - 订阅
    progress.addListener(_update);
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.center,
      children:[
        SizedBox(
          width: 200,
          height: 50,
          child: LinearProgressIndicator(
            value: progress.value,
            backgroundColor: Colors.grey,
          ),
        ),
        Text(progress.valueStr,style: TextStyle(color: Colors.white),)
      ],
    );
  }

  /// 2. 发布通知时,触发更新
  void _update() {
    setState(() {});
  }

  @override
  void dispose() {
    /// 3. 组件销毁时,移除监听
    progress.removeListener(_update);
    super.dispose();
  }
}

这样 ChangeNotifier 使用的一个小案例就介绍完了。大家可以自己在 FlutterUnit 中跑一跑,体验一下。下面来从源码的角度来分析一下 ChangeNotifier 的实现细节。


3. ChangeNotifier 源码分析

ChangeNotifier 类源码位于: flutter\lib\src\foundation\change_notifier.dart 首先,它是一个 mixin,说明该类型无法直接实例化对象 (混入类无构造函数)。其次,它实现了 Listenable 接口。

Listenable 是可监听对象的顶层接口,定义了 addListenerremoveListener 两个抽象方法。

代码语言:javascript
复制
abstract class Listenable {
  const Listenable();
  factory Listenable.merge(List<Listenable?> listenables) = _MergingListenable;

  void addListener(VoidCallback listener);

  void removeListener(VoidCallback listener);
}

ChangeNotifier 既然实现了 Listenable 接口,那自然核心任务是实现抽象方法的具体逻辑。下面是 ChangeNotifier 类的结构,其中核心是维护了 List<VoidCallback?> 类型的 _listeners 对象,作为一种订阅关系。


下面是添加监听的实现,调试中是详情页进入的时刻。在 addListener 处理完毕后,更新的回调函数将会被加入到 _listeners 回调列表中。

代码语言:javascript
复制
@override
void addListener(VoidCallback listener) {
  if (_count == _listeners.length) {
    if (_count == 0) {
      _listeners = List<VoidCallback?>.filled(1, null);
    } else {
      final List<VoidCallback?> newListeners =
          List<VoidCallback?>.filled(_listeners.length * 2, null);
      for (int i = 0; i < _count; i++) {
        newListeners[i] = _listeners[i];
      }
      _listeners = newListeners;
    }
  }
  _listeners[_count++] = listener;
}

ChangeNotifier#notifyListeners 方法中,将会变量 _count 次,触发 _listeners 列表对应索引的的回调函数。这就是通过函数对象,实现的添加监听和触发通知的一种机制。


4. ChangeNotifier 和 ValueNotifier

了解 ChangeNotifier 的使用之后,就非常好理解 ValueNotifier 。它支持一个泛型,继承自 ChangeNotifier ,实现 ValueListenable 接口。使用它可以监听某种特定类型的数据,从实现逻辑上来看就是在 set 时触发 notifyListeners 而言,也没有什么神奇的东西。

代码语言:javascript
复制
class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
  ValueNotifier(this._value) {
    /// 略...
  }

  @override
  T get value => _value;
  T _value;
  set value(T newValue) {
    if (_value == newValue) {
      return;
    }
    _value = newValue;
    notifyListeners();
  }

  @override
  String toString() => '${describeIdentity(this)}($value)';
}

总的来说 ChangeNotifier 提供了一个 添加监听 - 发布通知 的机制,对于单类型的数据有 ValueNotifier 派生类方便使用。对于多种类型的数据,或者触发监听时机希望灵活控制时,可以自己混入 ChangeNotifier 来处理。可监听对象对于 Flutter 而言是一个非常重要的存在, ChangeNotifier 只是其中非常重要的一支。

我们平时使用的 TabControllerScrollControllerTextEditingControllerFocusNode 等;另外,滑动机制中,手势事件产生的数据和视口感知的滑动偏移量,通过 ScrollPosition 来连接。它们都是 ChangeNotifier 的派生类,足以见得 ChangeNotifier 在 Flutter 中的分量。

那本文就到这了,后续还会带来更多的精彩内容,下次再见~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-10-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 数据的提供者与消费者
  • 2. 通过一个小案例了解 ChangeNotifier 的使用
  • 3. ChangeNotifier 源码分析
  • 4. ChangeNotifier 和 ValueNotifier
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档