Flutter笔记--通知

[复制链接]
发表于 2026-10-5 19:17:37 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×

Flutter 里的 Notification(通知)是一个容易被忽略但很趁手的机制。它和事件传递不同:事件(pointer event)从字面向下分发,而通知是从子节点向上冒泡。这意味着任意深度的子组件都能发一个通知,处在任意位置的父组件只要挂了 NotificationListener 就能收到,天然适合做跨组件的通信。本文把用法、冒泡原理和拦截逻辑讲清楚,配一个滚动进度条的完整例子。

一、为什么需要它

组件树深了以后,子组件想告诉父组件点什么事,常见做法有三种:回调(callback)、InheritedWidget、或状态管理库。这三种各有成本——回调要一层层往下传,InheritedWidget 适合共享数据而不是「一次性事件」,状态管理库对小场景偏重。

Notification 的价值在于不用在父子之间建立直接引用。子层打个响指,一路往上的祖先都能选择听或不听;中间任意一层如果想拦,直接拦下就行。滚动条同步、折叠面板、表单校验结果上报这类场景用它都挺顺。

二、三步接上监听器

  1. 创建 NotificationListener:在需要监听的 Widget 的 build 方法里,包一层 NotificationListener。
  2. 设置 onNotification 回调:这个回调负责处理收到的通知,可以在里面按类型分支写业务逻辑。
  3. 指定子 Widget:把要监听的组件作为 child 传进去。

三、完整例子:滚动时打印进度

下面这个例子监听的是滚动通知(ScrollNotification),滚动开始、滚动中、滚动结束各打一行日志日志,滚动过程中把进度算出来存进 state:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class NotificationTest extends StatefulWidget {
  @override
  State createState() {
    return _NotificationTest();
  }
}

class _NotificationTest extends State {
  int _progress = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NotificationListener(
        onNotification: (ScrollNotification notification) {
          if (notification is ScrollStartNotification) {
            print('开始滚动...');
          } else if (notification is ScrollUpdateNotification) {
            final currentPixel = notification.metrics.pixels;
            final totalPixel = notification.metrics.maxScrollExtent;
            double progress = currentPixel / totalPixel;
            setState(() {
              _progress = (progress * 100).toInt();
              print('滚动..._progress: $_progress');
            });
            print('正在滚动: $currentPixel - $totalPixel');
          } else if (notification is ScrollEndNotification) {
            print('结束滚动...');
          }
          // 返回 false 表示不阻止事件继续向上冒泡
          return false;
        },
        child: Stack(
          alignment: Alignment.bottomRight,
          children: [
            ListView.builder(
              itemCount: 100,
              itemExtent: 60,
              itemBuilder: (BuildContext context, int index) {
                return ListTile(title: Text("Item $index"));
              },
            ),
          ],
        ),
      ),
    );
  }
}

几个细节值得注意:

  • notification.metrics.pixels 是当前滚动位置,maxScrollExtent 是总可滚动距离,两者相除就是进度。
  • 回调里调 setState 会触发重建,高频滚动时记得考虑性能,比如只在整数百分比变化时才 setState。
  • 代码里用的是 is 关键字做类型判断,写的时候容易漏写成 isScrollStartNotification 这样,编译器会直接报错,留意一下。

四、冒泡原理:dispatch 做了什么

整个传递链路其实不复杂:

  1. 通知发起:Widget 树里任意节点都可以分发通知,做法是调用 notification.dispatch(context),context 是当前节点的上下文。
  2. 向上遍历:dispatch 内部会调 context.visitAncestorElements(visitAncestor),这个方法沿树往上遍历父元素,对每一个父元素执行 visitAncestor 回调。
  3. 逐个询问:每个父元素如果有 NotificationListener,就能在回调里拿到这个通知。
  4. 中止冒泡:如果某个 listener 的 onNotification 返回 true,表示这一层已经处理掉了,不再往上传;返回 false 就继续向上,直到遇到下一个 NotificationListener 或者传到树顶。

理解「是 dispatch 触发 visitAncestorElements 往上走」这一点,就明白了为什么 Notification 只能向上、不能向下——它和 Touch 事件的分发方向是反的。

五、自定义通知

上面用的是内置 ScrollNotification。实际业务里更常用的是自己定义一个:

class DownloadProgressNotification extends Notification {
  final int percent;
  DownloadProgressNotification(this.percent);

  @override
  bool? notifyChildren() => true;
}

发送方在任意 Widget 里:

DownloadProgressNotification(60).dispatch(context);

接收方在祖先层用 NotificationListener<DownloadProgressNotification> 就能精确收到自己的类型,不会被子层的其他通知干扰——这是它比"全局变量 + setState"干净的地方。

六、和 InheritedWidget / 状态管理的取舍

  • 用 Notification:一次性的、向上传达的事件(滚动了、刷新了、校验失败了),且发出方不需要知道谁在听。
  • 用 InheritedWidget:需要向下共享的、有状态的数据(主题、语言、登录态)。
  • 用状态管理:跨页面、需要持久化、有多个组件读写同一份状态的情况。

判断标准很简单:如果你在写「发完就不管了,谁爱听谁听」的代码,Notification 就是最省的那个。

七、常见坑

  • 忘了 return false:默认返回 false 继续冒泡;如果你在回调里提前 return 了别的分支,可能导致上层 listener 收不到,排查时先确认返回值。
  • 在 build 里 dispatch:dispatch 会触发 setState 循环,表现是界面无限重绘卡死。要发通知就把触发点放到事件回调里。
  • Listener 位置太低:listener 挂在的子树里如果压根没有触发源,就永远等不到。确认 dispatch 的那个 context 在 listener 的子树内。
  • 高频通知 + setState:快速滚动时每帧都 setState 会掉帧,加个阈值(比如变化超过 1% 才更新)。

回过头看,Notification 这套机制的核心就一句话:任意节点 dispatch,沿树向上冒泡,中间谁想拦就拦。把这条链路记牢,剩下的用起来就很自然了。

qidao123.com企服评测-应用市场 编辑提示

回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表