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] 
// 并通过将其添加到子树中来通知其后代关于滚动通知
相关推荐
前端大卫3 小时前
Vue 和 React 受控组件的区别!
前端
Hy行者勇哥4 小时前
前端代码结构详解
前端
练习时长一年4 小时前
Spring代理的特点
java·前端·spring
水星记_4 小时前
时间轴组件开发:实现灵活的时间范围选择
前端·vue
2501_930124705 小时前
Linux之Shell编程(三)流程控制
linux·前端·chrome
潘小安5 小时前
『译』React useEffect:早知道这些调试技巧就好了
前端·react.js·面试
@大迁世界5 小时前
告别 React 中丑陋的导入路径,借助 Vite 的魔法
前端·javascript·react.js·前端框架·ecmascript
EndingCoder5 小时前
Electron Fiddle:快速实验与原型开发
前端·javascript·electron·前端框架
EndingCoder5 小时前
Electron 进程模型:主进程与渲染进程详解
前端·javascript·electron·前端框架