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
向下传递给子树。
🔎 关键点解析
-
继承 InheritedWidget
InheritedWidget
是 Flutter 的一种数据共享机制,通常用来在 widget 树中向下传递状态。- 子树里的 widget 可以通过
context.dependOnInheritedWidgetOfExactType<_ScrollNotificationObserverScope>()
来拿到它。
-
构造函数
- 参数里必须传
child
(InheritedWidget 标配)。 - 还必须传
scrollNotificationObserverState
,保存到_scrollNotificationObserverState
- 参数里必须传
-
updateShouldNotify
- 只有当
_scrollNotificationObserverState
不同的时候,InheritedWidget 才会通知依赖它的 widget 重新构建。 - 实际上,这个 state 一般不会变(除非 widget 被重新创建),所以这里大多数情况返回
false
。
- 只有当
_ListenerEntry
scala
final class _ListenerEntry extends LinkedListEntry<_ListenerEntry> {
_ListenerEntry(this.listener);
final ScrollNotificationCallback listener;
}
🔑 关键点解析
-
final class
- Dart 3 引入的新修饰符:
final class
表示这个类不能被继承,但可以被实例化。 - 用来确保
_ListenerEntry
只在这里作为"叶子类"使用。
- Dart 3 引入的新修饰符:
-
继承
LinkedListEntry<_ListenerEntry>
LinkedListEntry
是 Dart 内置的 双向链表节点基类。- 每个
_ListenerEntry
既是一个监听器,也自带"链表指针"(前驱/后继引用)。 - 这样就能把一堆
_ListenerEntry
串成一个LinkedList<_ListenerEntry>
,高效增删节点。
👉 好处:相比普通
List
,LinkedList
的 插入/删除监听器 是 O(1),而不是 O(n)。 -
listener
字段-
保存一个
ScrollNotificationCallback
,即:initypedef 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;
}
}
}
🔍 逐行解析
-
方法注释
- 移除指定的
ScrollNotificationCallback
(滚动通知回调函数)。
- 移除指定的
-
assert(_debugAssertNotDisposed());
- 和
addListener
一样,先检查当前对象是否已被销毁。 - 避免在已失效的状态下操作
_listeners
。
- 和
-
for (final _ListenerEntry entry in _listeners!)
- 遍历
_listeners
(链表)。 _ListenerEntry
节点里保存了回调函数。
- 遍历
-
if (entry.listener == listener)
- 找到目标回调。
-
entry.unlink();
- 调用
LinkedListEntry.unlink()
将该节点从链表中移除。 - 不需要手动修改前后节点引用,
LinkedListEntry
已经封装好。
- 调用
-
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]
// 并通过将其添加到子树中来通知其后代关于滚动通知