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

1. 数据的提供者与消费者

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

有三个铁粉跟我说: "你发新文章的时候跟我说一声"。

之后我发布文章后,分享给了他们三个人。

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

发布者是博主,是数据的提供者,也是通知事件的执行人。

订阅者是粉丝,是数据的消费者,需要依赖数据完成需求。

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


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

案例演示 监听-通知关系

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

下面,我们来完成上面下载进度的模拟案例,演示一下 ChangeNotifier 的使用。首先来分析一下:

在视图方面,主页面中有一个圆形的进度条 HomeProgressView 组件;点击头部栏左上角进入详情页,其中有一个矩形的进度条 DetailProgressView 组件。

在视图方面,主界面右下角按钮点击时,进度数据将会不断增加,直到 1 ;两个进度相关的组件,需要感知进度数值的变化,从而更新进度呈现。

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


  • 数据方面处理

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

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

dart 复制代码
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% 的进度,以此模拟下载进度数值的增加。

dart 复制代码
---->[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\]. 在状态类销毁后,要及时`移除监听`。否则仍会在销毁后,触发更新,导致异常。

dart 复制代码
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 两个抽象方法。

dart 复制代码
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 回调列表中。

dart 复制代码
@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 而言,也没有什么神奇的东西。

dart 复制代码
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 中的分量。

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

相关推荐
*才华有限公司*22 分钟前
安卓前后端连接教程
android
氦客1 小时前
Android Compose中的附带效应
android·compose·effect·jetpack·composable·附带效应·side effect
雨白1 小时前
Kotlin 协程的灵魂:结构化并发详解
android·kotlin
我命由我123451 小时前
Android 开发问题:getLeft、getRight、getTop、getBottom 方法返回的值都为 0
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
Modu_MrLiu1 小时前
Android实战进阶 - 用户闲置超时自动退出登录功能详解
android·超时保护·实战进阶·长时间未操作超时保护·闲置超时
Jeled2 小时前
Android 网络层最佳实践:Retrofit + OkHttp 封装与实战
android·okhttp·kotlin·android studio·retrofit
信田君95272 小时前
瑞莎星瑞(Radxa Orion O6) 基于 Android OS 使用 NPU的图片模糊查找APP 开发
android·人工智能·深度学习·神经网络
tangweiguo030519872 小时前
Kotlin 实现 Android 网络状态检测工具类
android·网络·kotlin
nvvas3 小时前
Android Studio JAVA开发按钮跳转功能
android·java·android studio
怪兽20144 小时前
Android多进程通信机制
android·面试