flutter滚动视图之ScrollNotificationObserve源码解析(十)

ScrollNotificationCallback

ini 复制代码
typedef ScrollNotificationCallback = void Function(ScrollNotification notification);

🔎 含义解析

  • 它定义了一个 函数类型别名

  • 表示一种回调函数,签名是:

    • 入参:ScrollNotification notification
    • 返回:void
  • 用于 ScrollNotificationObserver 内部,存储和调用监听器。

换句话说,每个 ScrollNotificationCallback 就是一个只要有滚动通知,就执行的监听器

📌 和 NotificationListener<ScrollNotification> 的区别

NotificationListener 中,回调是:

arduino 复制代码
bool onNotification(ScrollNotification notification) { ... }
  • 返回值 bool 有意义:决定是否要继续向上传递通知(true = 截断冒泡,false = 继续冒泡)。
  • 只能有 一个 监听器。

而在 ScrollNotificationObserver 里:

ini 复制代码
typedef ScrollNotificationCallback = void Function(ScrollNotification notification);
  • 没有返回值 ,监听器无法阻止冒泡
  • 支持注册 多个回调,统一存放在一个 list 里。
  • 每个回调都会被调用,不会因为返回值不同而影响其他回调。

_ScrollNotificationObserverScope

scala 复制代码
class _ScrollNotificationObserverScope extends InheritedWidget {
  const _ScrollNotificationObserverScope({
    required super.child,
    required ScrollNotificationObserverState scrollNotificationObserverState,
  }) : _scrollNotificationObserverState = scrollNotificationObserverState;

  final ScrollNotificationObserverState _scrollNotificationObserverState;

  @override
  bool updateShouldNotify(_ScrollNotificationObserverScope old) =>
      _scrollNotificationObserverState != old._scrollNotificationObserverState;
}

这段代码是 ScrollNotificationObserver 内部用的 InheritedWidget ,专门用来把 ScrollNotificationObserverState 向下传递给子树。

🔎 关键点解析

  1. 继承 InheritedWidget

    • InheritedWidget 是 Flutter 的一种数据共享机制,通常用来在 widget 树中向下传递状态。
    • 子树里的 widget 可以通过 context.dependOnInheritedWidgetOfExactType<_ScrollNotificationObserverScope>() 来拿到它。
  2. 构造函数

    • 参数里必须传 child(InheritedWidget 标配)。
    • 还必须传 scrollNotificationObserverState,保存到 _scrollNotificationObserverState
  3. updateShouldNotify

    • 只有当 _scrollNotificationObserverState 不同的时候,InheritedWidget 才会通知依赖它的 widget 重新构建。
    • 实际上,这个 state 一般不会变(除非 widget 被重新创建),所以这里大多数情况返回 false

_ListenerEntry

scala 复制代码
final class _ListenerEntry extends LinkedListEntry<_ListenerEntry> {
  _ListenerEntry(this.listener);
  final ScrollNotificationCallback listener;
}

🔑 关键点解析

  1. final class

    • Dart 3 引入的新修饰符:final class 表示这个类不能被继承,但可以被实例化。
    • 用来确保 _ListenerEntry 只在这里作为"叶子类"使用。
  2. 继承 LinkedListEntry<_ListenerEntry>

    • LinkedListEntry 是 Dart 内置的 双向链表节点基类
    • 每个 _ListenerEntry 既是一个监听器,也自带"链表指针"(前驱/后继引用)。
    • 这样就能把一堆 _ListenerEntry 串成一个 LinkedList<_ListenerEntry>,高效增删节点。

    👉 好处:相比普通 ListLinkedList插入/删除监听器 是 O(1),而不是 O(n)。

  3. listener 字段

    • 保存一个 ScrollNotificationCallback,即:

      ini 复制代码
      typedef ScrollNotificationCallback = void Function(ScrollNotification notification);
    • 也就是实际的回调函数,每次滚动通知时要执行的代码。

ScrollNotificationObserver

scala 复制代码
/// 当子树中有滚动发生时,会通知它的所有监听器。
///
/// 向祖先 [ScrollNotificationObserver] 添加监听器的方式:
///
/// ```dart
/// ScrollNotificationObserver.of(context).addListener(_listener);
/// ```
///
/// 从祖先 [ScrollNotificationObserver] 移除监听器的方式:
///
/// ```dart
/// ScrollNotificationObserver.of(context).removeListener(_listener);
/// ```
///
/// 在共享同一个祖先 [ScrollNotificationObserver] 的 StatefulWidget 中,
/// 通常会在 [State.didChangeDependencies] 中添加监听器(必要时先移除旧的监听器),
/// 并在 [State.dispose] 方法中移除监听器。
///
/// 任何符合 [ScrollNotificationCallback] 签名的函数都可以作为监听器:
///
/// ```dart
/// // (例如,在一个 StatefulWidget 中)
/// void _listener(ScrollNotification notification) {
///   // 执行某些操作,比如 setState()
/// }
/// ```
///
/// 此组件与 [NotificationListener] 类似,但它支持 **监听器列表**
/// 而不仅仅是单个监听器,并且它的监听器会无条件运行,
/// 不需要依赖布尔返回值来决定是否继续分发通知。
///
/// {@tool dartpad}
/// 本示例展示了一个"回到顶部"按钮,它使用 [ScrollNotificationObserver]
/// 来监听 [ListView] 的滚动通知。只有当用户向下滚动时按钮才会显示,
/// 当按钮被点击时,会将 [ListView] 的滚动位置以动画方式滚动回顶部。
///
/// ** 示例代码见:examples/api/lib/widgets/scroll_notification_observer/scroll_notification_observer.0.dart **
/// {@end-tool}
class ScrollNotificationObserver extends StatefulWidget {
  /// 创建一个 [ScrollNotificationObserver]。
  const ScrollNotificationObserver({super.key, required this.child});

  /// 此组件所包裹的子树。
  final Widget child;

  /// 返回离给定 context 最近的 [ScrollNotificationObserver] 实例。
  ///
  /// 如果没有找到祖先 [ScrollNotificationObserver],则返回 null。
  ///
  /// 调用此方法会在 [context] 中创建一个对最近的
  /// [ScrollNotificationObserver] 的依赖(如果存在)。
  ///
  /// 另见:
  ///
  /// * [ScrollNotificationObserver.of],与此方法类似,但在找不到祖先
  ///   [ScrollNotificationObserver] 时会直接断言报错。
  static ScrollNotificationObserverState? maybeOf(BuildContext context) {
    return context
        .dependOnInheritedWidgetOfExactType<_ScrollNotificationObserverScope>()
        ?._scrollNotificationObserverState;
  }

ScrollNotificationObserverState

scala 复制代码
/// 由 [ScrollNotificationObserver.of] 返回的
/// [ScrollNotificationObserver] 的监听器列表状态。
///
/// [ScrollNotificationObserver] 与 [NotificationListener] 类似,
/// 不同之处在于它支持 **监听器列表**,而不仅仅是单个监听器;
/// 并且它的所有监听器会无条件执行,
/// 不需要依赖布尔返回值来决定是否继续分发通知。
class ScrollNotificationObserverState extends State<ScrollNotificationObserver> {}

_listeners

swift 复制代码
// 用来存储所有监听器的链表。
// 每个监听器会被包装成一个 _ListenerEntry 节点,
// 以便在链表中快速插入或移除。
LinkedList<_ListenerEntry>? _listeners = LinkedList<_ListenerEntry>();

📌 为什么要用 LinkedList

ScrollNotificationObserverState 里,_listeners 用来存储多个 滚动通知监听器

  • 每个监听器被包装成 _ListenerEntry(继承自 LinkedListEntry),
  • 通过链表的结构,可以快速插入和删除监听器,性能比 List 更好(尤其是频繁 add/remove 时)。

addListener

scss 复制代码
/// 添加一个滚动通知监听器 [listener]。
/// 每当某个子孙节点发生滚动时,这个监听器都会被调用。
void addListener(ScrollNotificationCallback listener) {
  // 确保当前 State 没有被销毁
  assert(_debugAssertNotDisposed());

  // 将 listener 包装成一个链表节点,并加入链表
  _listeners!.add(_ListenerEntry(listener));
}

_listeners!.add(_ListenerEntry(listener));

  • _listeners 是一个 LinkedList<_ListenerEntry>
  • listener 被包装成 _ListenerEntry(listener),再插入链表里。
  • 每一个 _ListenerEntry 就是一个节点,保存着一个监听器函数。

removeListener

scss 复制代码
/// Remove the specified [ScrollNotificationCallback].
void removeListener(ScrollNotificationCallback listener) {
  assert(_debugAssertNotDisposed());
  for (final _ListenerEntry entry in _listeners!) {
    if (entry.listener == listener) {
      entry.unlink();
      return;
    }
  }
}

🔍 逐行解析

  1. 方法注释

    • 移除指定的 ScrollNotificationCallback(滚动通知回调函数)。
  2. assert(_debugAssertNotDisposed());

    • addListener 一样,先检查当前对象是否已被销毁。
    • 避免在已失效的状态下操作 _listeners
  3. for (final _ListenerEntry entry in _listeners!)

    • 遍历 _listeners(链表)。
    • _ListenerEntry 节点里保存了回调函数。
  4. if (entry.listener == listener)

    • 找到目标回调。
  5. entry.unlink();

    • 调用 LinkedListEntry.unlink() 将该节点从链表中移除。
    • 不需要手动修改前后节点引用,LinkedListEntry 已经封装好。
  6. return;

    • 移除成功后立即返回,不继续遍历。

_notifyListeners

php 复制代码
/// 将 [notification] 分发给所有已注册的监听器。
void _notifyListeners(ScrollNotification notification) {
  // 确保当前 State 未被销毁
  assert(_debugAssertNotDisposed());

  // 如果没有监听器,直接返回
  if (_listeners!.isEmpty) {
    return;
  }

  // 复制一份监听器快照,避免遍历过程中链表被修改
  final List<_ListenerEntry> localListeners = List<_ListenerEntry>.of(_listeners!);

  // 遍历所有监听器并回调
  for (final _ListenerEntry entry in localListeners) {
    try {
      // 确保该监听器仍在链表中(未被移除)
      if (entry.list != null) {
        entry.listener(notification);
      }
    } catch (exception, stack) {
      // 捕获单个监听器的异常,避免影响其他监听器
      FlutterError.reportError(
        FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'widget library',
          context: ErrorDescription('while dispatching notifications for $runtimeType'),
          informationCollector: () => <DiagnosticsNode>[
            DiagnosticsProperty<ScrollNotificationObserverState>(
              'The $runtimeType sending notification was',
              this,
              style: DiagnosticsTreeStyle.errorProperty,
            ),
          ],
        ),
      );
    }
  }
}

📌 总结:

  • 复制快照 → 保证遍历安全。
  • try/catch → 单个监听器出错不影响整体。
  • entry.list != null → 确保监听器还在链表。

build

less 复制代码
@protected
@override
Widget build(BuildContext context) {
  return NotificationListener<ScrollMetricsNotification>(
    // 捕获滚动度量变化(例如 Viewport 尺寸改变)
    onNotification: (ScrollMetricsNotification notification) {
      // 将 ScrollMetricsNotification 转换为 ScrollUpdateNotification 广播给所有监听器
      _notifyListeners(notification.asScrollUpdate());
      return false; // 不阻止通知继续向上冒泡
    },
    child: NotificationListener<ScrollNotification>(
      // 捕获所有滚动相关通知
      onNotification: (ScrollNotification notification) {
        // 广播给所有注册的监听器
        _notifyListeners(notification);
        return false; // 不阻止通知继续向上冒泡
      },
      child: _ScrollNotificationObserverScope(
        // 提供当前状态对象的引用给子树,供 ScrollNotificationObserver.of(context) 使用
        scrollNotificationObserverState: this,
        child: widget.child, // 用户传入的子树
      ),
    ),
  );
}

dispose

less 复制代码
@protected
@override
void dispose() {
  // 确保当前状态对象未被销毁
  assert(_debugAssertNotDisposed());

  // 清空监听器链表,释放对回调的引用
  _listeners = null;

  // 调用父类的销毁逻辑
  super.dispose();
}

特别

ruby 复制代码
// Scaffold 组件包含一个 [ScrollNotificationObserver] 
// 它被 [AppBar] 用于其滚动隐藏行为 
// // 我们可以使用 [ScrollNotificationObserver.maybeOf] 从 Scaffold 组件的后代中 
// 获取该 [ScrollNotificationObserver] 的状态 
// // 如果你没有使用 [Scaffold] 组件,可以创建一个 [ScrollNotificationObserver] 
// 并通过将其添加到子树中来通知其后代关于滚动通知
相关推荐
咬人喵喵4 小时前
CSS Flexbox:拥有魔法的排版盒子
前端·css
LYFlied4 小时前
TS-Loader 源码解析与自定义 Webpack Loader 开发指南
前端·webpack·node.js·编译·打包
yzp01124 小时前
css收集
前端·css
暴富的Tdy4 小时前
【Webpack 的核心应用场景】
前端·webpack·node.js
遇见很ok4 小时前
Web Worker
前端·javascript·vue.js
风舞红枫4 小时前
前端可配置权限规则案例
前端
zhougl9964 小时前
前端模块化
前端
暴富暴富暴富啦啦啦4 小时前
Map 缓存和拿取
前端·javascript·缓存
天问一4 小时前
前端Vue使用js-audio-plugin实现录音功能
前端·javascript·vue.js
dodod20124 小时前
Ubuntu24.04.3执行sudo apt install yarnpkg 命令失败的原因
java·服务器·前端