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


theme: cyanosis

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

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

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

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

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

image.png

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


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

| 案例演示 | 监听-通知关系 | | --- | --- | | 184.gif | 未标题-1.png |


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

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

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

image.png


  • 数据方面处理

由于 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 createState() => _DetailProgressViewState(); }

class _DetailProgressViewState extends State {

@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 接口。

image.png

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

```dart abstract class Listenable { const Listenable(); factory Listenable.merge(List listenables) = _MergingListenable;

void addListener(VoidCallback listener);

void removeListener(VoidCallback listener); } ```

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

image.png


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

image.png

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 列表对应索引的的回调函数。这就是通过函数对象,实现的添加监听和触发通知的一种机制。

image.png


4. ChangeNotifier 和 ValueNotifier

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

```dart class ValueNotifier extends ChangeNotifier implements ValueListenable { 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 中的分量。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/107851.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++学习: 文件I/O

作者: 苏丙榅 原文链接: https://subingwen.cn/c/file/ 文章目录 1. 文件概述1.1 什么是文件I/O1.2 磁盘文件分类 2. 文件的打开和关闭2.1 文件指针2.2 打开文件 2.3 关闭文件3. 文件的读写3.1 按照字符读写文件3.1.1 写文件3.1.2 读文件3.1.3 EOF 3.2 按照行读写文件3.2.1 写文…

[架构之路-237]:目标系统 - 纵向分层 - 网络通信 - DNS的递归查询和迭代查询

目录 一、DNS协议与DNS系统架构 1.1 什么是DNS协议 1.2 为什么需要DNS协议 1.3 DNS系统架构 二、DNS系统的查询方式 2.1 递归与迭代的比较 2.2 DNS递归查询 2.3 DNS迭代查询 一、DNS协议与DNS系统架构 1.1 什么是DNS协议 DNS&#xff08;Domain Name System&#xff…

2023年中国商业版服务器操作系统市场发展规模分析:未来将保持稳定增长[图]

服务器操作系统一般指的是安装在大型计算机上的操作系统&#xff0c;比如Web服务器、应用服务器和数据库服务器等&#xff0c;是企业IT系统的基础架构平台&#xff0c;也是按应用领域划分的三类操作系统之一。同时服务器操作系统也可以安装在个人电脑上。 服务器操作系统分类 …

WSL Ubuntu 22.04.2 LTS 安装paddlepaddle-gpu==2.5.1踩坑日记

环境是wsl的conda环境。 使用conda安装paddlepaddle-gpu: conda install paddlepaddle-gpu2.5.1 cudatoolkit11.7 -c https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/Paddle/ -c conda-forge 等待安装... 报错处理&#xff1a; (1)PreconditionNotMetError: Cannot lo…

excel管理接口测试用例

闲话休扯&#xff0c;上需求&#xff1a;自动读取、执行excel里面的接口测试用例&#xff0c;测试完成后&#xff0c;返回错误结果并发送邮件通知。 分析&#xff1a; 1、设计excel表格 2、读取excel表格 3、拼接url&#xff0c;发送请求 4、汇总错误结果、发送邮件 开始实现…

LeetCode24.两两交换链表中的节点

这道题还是比较简单&#xff0c;我看完题目的想法就是借助一个第三变量&#xff0c;来改变两个节点的指针的指向&#xff0c;比如我要改变1和2节点的指向&#xff0c;我用h1表示节点1&#xff0c;我创建一个新的节点ans&#xff0c;先拿ans指向节点2&#xff08;ans.next h1.n…

【面试高频题】难度 1/5,经典树的搜索(多语言)

题目描述 这是 LeetCode 上的 「109. 有序链表转换二叉搜索树」 &#xff0c;难度为 「中等」 Tag : 「二叉树」、「树的搜索」、「分治」、「中序遍历」 给定一个单链表的头节点 head&#xff0c;其中的元素 按升序排序 &#xff0c;将其转换为高度平衡的二叉搜索树。 本题中&…

软件工程与计算(十四)详细设计中面向对象方法下的模块化

一.面向对象中的模块 1.类 模块化是消除软件复杂度的一个重要方法&#xff0c;每个代码片段相互独立&#xff0c;这样能够提高可维护性。在面向对象方法中&#xff0c;代码片段最重要的类&#xff0c;整个类的所有代码联合起来构成独立的代码片段。 模块化希望代码片段由两部…

CSS的布局 Day03

一、显示模式&#xff1a; 网页中HTML的标签多种多样&#xff0c;具有不同的特征。而我们学习盒子模型、使用定位和弹性布局把内容分块&#xff0c;利用CSS布局使内容脱离文本流&#xff0c;使用定位或弹性布局让每块内容摆放在想摆放的位置&#xff0c;让网站页面布局更合理、…

Android ViewBinding和DataBinding功能作用区别

简述 ViewBinding和DataBinding都是用于在 Android 应用程序中处理视图的工具&#xff0c;但它们有不同的作用和用途。 ViewBinding: ViewBinding 是 Android Studio 的一个工具&#xff0c;用于生成一个绑定类&#xff0c;能够轻松访问 XML 布局文件中的视图。ViewBinding 为…

ubuntu16.04安装vscode遇到的code 依赖于 libnss3 (>= 2:3.30)解决

ubuntu16.04安装vscode遇到的code 依赖于 libnss3 (> 2:3.30)&#xff1b;然而&#xff1a;系统中 libnss3:amd64 的版本为 2:3.28.4-0ubuntu0.16.04.14解决方法 一开始下载了最新版本的vscode&#xff0c;安装时出现了上面的错误状况&#xff0c;最新版本的依赖库版本过低的…

Idea怎么配置Maven才能优先从本地仓库获取依赖

网上的方法 : 在设置中搜索 Runner ,在VM Option中设置参数 -DarchetypeCataloginternal删除 解压后的依赖包中的 _remote.repositories m2e-lastUpdated.properties *.lastUpdated 文件。 上边都没有效果 最终的解决方法&#xff0c;修改maven配置文件settings.xml 主要两个…

LeetCode讲解篇之198. 打家劫舍

LeetCode讲解篇之198. 打家劫舍 文章目录 LeetCode讲解篇之198. 打家劫舍题目描述题解思路题解代码 题目描述 题解思路 该问题可以通过递推来完成 递推公式&#xff1a; 前n间房的最大金额 max&#xff08;前n-1间房的最大金额&#xff0c; 前n-2间房的最大金额第n-1间房的最…

MySQL学习(四)——事务与存储引擎

文章目录 1. 事务1.1 概念1.2 事务操作1.2.1 未设置事务1.2.2 控制事务 1.3 事务四大特性1.4 并发事务问题1.5 事务隔离级别 2. 存储引擎2.1 MySQL体系结构2.2 存储引擎2.3 存储引擎的特点2.3.1 InnoDB2.3.2 MyISAM2.3.3 Memory2.3.4 区别和比较 1. 事务 1.1 概念 事务 是一组…

深度分析c+引用的本质以及引用与指针的区别

文章目录 引用的概念引用的定义引用的特性引用的权限问题引用的使用方式引用作参数引用作返回值指针的本质引用和指针的区别 引用的概念 引用不是新定义一个变量&#xff0c;而是给已存在变量取了一个别名&#xff0c;编译器不会为引用变量开辟内存空间&#xff0c;它和它引用…

C语言——二周目——数据在内存中的存储

目录 一、整数的存储方式 二、浮点数的存储方式 一、整数的存储方式 因为CPU只有加法器&#xff0c;所以对于整型来说&#xff0c;数据在内存中通常采用补码的方式进行储存。 在这里复习一下原码、反码、补码。 正数和无符号数的原码、反码、补码相同&#xff1b; 负数的原…

depcheck检查项目依赖的安装情况-帮你解决各种项目运行灵异事件

depcheck检查项目缺失的依赖 depcheck介绍与安装介绍安装 depcheck使用基础使用注意 进阶使用 删除多余的依赖注意 depcheck介绍与安装 介绍 工作中&#xff0c;以下的场景恐怕大家都有经历过&#xff1a; 从代码仓库上面 clone 的项目&#xff0c;自己本地一运行就报错… 用…

网工实验笔记:MQC原理与配置

一、概述 MQC&#xff08;Modular QoS Command-Line Interface&#xff0c;模块化QoS命令行&#xff09;是指通过将具有某类共同特征的数据流划分为一类&#xff0c;并为同一类数据流提供相同的服务&#xff0c;也可以对不同类的数据流提供不同的服务。 MQC三要素 流分类&am…

流量代理——正向代理

流量代理 正向代理和反向代理 正向代理就是客户端和其他所有服务器&#xff08;重点&#xff1a;所有&#xff09;的代理者。 反向代理是客户端和所要代理的服务器之间的代理。 流量转发工具 需要放在拿到shell的服务器上可使用 lcx&#xff1a;端口流量转发&#xff0c;不具…

Motorola IPMC761 使用边缘TPU加速神经网络

Motorola IPMC761 使用边缘TPU加速神经网络 人工智能(AI)和机器学习(ML)正在塑造和推进复杂的自动化技术解决方案。将这些功能集成到硬件中&#xff0c;解决方案可以识别图像中的对象&#xff0c;分析和检测模式中的异常或找到关键短语。这些功能对于包括但不限于自动驾驶汽车…