【Flutter 组件集录】NotificationListener| 8月更文挑战
前言:
1. 引子
在研究 ScrollView 源码时,有个很有意思的收获。这里作为引子,来引入 NotificationListener 组件。下面是 ScrollView#build 源码中的一部分,可以看出,当 keyboardDismissBehavior 为 onDrag 时,所构建的组件上层会嵌套一个 NotificationListener 组件,并在 onNotification 中进行逻辑性处理。

其中 ScrollViewKeyboardDismissBehavior 是只有两个元素的枚举。
enum ScrollViewKeyboardDismissBehavior {
manual,
onDrag,
}
ListView 继承自ScrollView ,构造中的 keyboardDismissBehavior 参数,会为 ScrollView 中定义的该成员进行初始化。测试的核心代码如下:

manual 和 onDrag 的效果如下:当前 键盘弹出时,如果为 manual ,列表滑动过程中键盘不会主动隐藏 。为 onDrag 时,滑动列表时,键盘会主动隐藏 。
| manual | onDrag |
|---|---|
![]() | ![]() |
通过源码中的一个小细节处理,我们能够清楚地认识到 NotificationListener 价值。它可以监听滑动的过程,回调出相关的数据让使用者进行逻辑处理。
2. 认识 NotificationListener
首先 NotificationListener 是一个 StatelessWidget ,接受一个 Notification 族 泛型,构造方法中必须传入一个 child 组件,可以设置 onNotification 的监听。

onNotification 成员的类型为 NotificationListenerCallback<T> ,可以看出它是一个函数类型,返回 bool 值。入参为 T 泛型对象,且必须是 Notification 子类 。也就是说,该函数会回调出一个数据,并且返回一个用于控制某个逻辑的标识。
---->[NotificationListener#onNotification 声明]----
final NotificationListenerCallback<T>? onNotification;
typedef NotificationListenerCallback<T extends Notification> =
bool Function(T notification);
既然作为一个 StatelessWidget,那最重要的当属 build 方法。 但从源码中可以看出,该组件直接返回使用者传入的 child ,也就是说,它并不关心组件的构建逻辑。

最后,该类中还有一个私有方法 _dispatch ,该方法中需要传入 Notification 对象,可以看出,这里是使用者传入的 onNotification 方法触发场合。

3.认识 Notification
上面涉及到了很多处 Notification ,如果不了解这个类,那么很难对 NotificationListener 有全面的认识。Notification 是一个 抽象类 ,它没有继承任何类。其中有两个普通方法 visitAncestor 和 dispatch。

既然 Notification 是抽象类,那么并不能直接构造对象,所以 Flutter 框架中自然要提供相关的实现类,如下是 Notification 的众多实现类,包括引子中的 ScrollUpdateNotification 。这样我们就知道能监听到哪些 Notification 。

4.认识 NotificationListener 的使用
比如下面我们通过 NotificationListener 监听 ScrollUpdateNotification ,这样滑动时_onNotification 回调就可以回调出 notification 滑动数据。我们就可以根据这个对象进行相关逻辑处理。

class ListViewDemo extends StatelessWidget {
const ListViewDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return NotificationListener<ScrollUpdateNotification>(
onNotification: _onNotification,
child: ListView(
children: List.generate(
60,
(index) => ItemBox(index: index, )).toList()),
);
}
bool _onNotification(ScrollUpdateNotification notification) {
print('====dragDetails:${notification.dragDetails}'
'====pixels:${notification.metrics.pixels}');
return false;
}
}
测试条目单体如下,使用 ItemBox 组件进行展示。

class ItemBox extends StatelessWidget {
final int index;
const ItemBox({Key? key, required this.index}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1 / window.devicePixelRatio,
))),
height: 56,
child: Text(
'第 $index 个',
style: TextStyle(fontSize: 20),
),
);
}
}
我们可以监听任意的 Notification 类型,比如下面的 OverscrollNotification,这个监听将会在列表滑动到最顶端或最底端时被触发,在回调的数据中可以得到越界的尺寸 overscroll 。

class ListViewDemo extends StatelessWidget {
const ListViewDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return NotificationListener<OverscrollNotification>(
onNotification: _onNotification,
child: ListView(
children: List.generate(
60,
(index) => ItemBox( index: index,)).toList()),
);
}
bool _onNotification(OverscrollNotification notification) {
print('====dragDetails:${notification.dragDetails}'
'====pixels:${notification.metrics.pixels}'
'=====overscroll:${notification.overscroll}');
return false;
}
}
除了对某一种 Notification 监听,我们也可以通过父级来监听多个 Notification,下面是监听顶层抽象 Notification 类的代码。简单上滑后日志如下,可以看出,这样能够同时监听到多种类型的 Notification 通知,我们可以通过类型判断来进行区分。

class ListViewDemo extends StatelessWidget {
const ListViewDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return NotificationListener<Notification>(
onNotification: _onNotification,
child: ListView(
children: List.generate(
60,
(index) => ItemBox( index: index,)).toList()),
);
}
bool _onNotification(Notification notification) {
print('====Notification type:${notification.runtimeType}======');
return false;
}
}
5. 源码中对 NotificationListener 的使用
最经典的当属 Scrollbar 源码中对 NotificationListener 的使用,它监听 ScrollNotification 的五种通知,通过 _handleScrollNotification 进行处理。

这样只要在 ListView 外层嵌套一个 Scrollbar ,在滑动过程中右侧就可以出现指示器。
Scrollbar(
child: ListView(
children: List.generate(
60,
(index) => ItemBox(index: index,)).toList()),
),

另外 RefreshIndicator 组件内部也是基于监听 ScrollNotification 和 OverscrollIndicatorNotification 通知进行实现的。

6. NotificationListener 监听中返回值的作用
从源码中可以看出,当返回 false 则表示通知可以继续向上层节点分发。反之也就意味着通知被截断。

比如下面代码,将 NotificationListener 放在 Scrollbar 下方,监听时返回 true。这样 ListView 的滑动事件向上分发时,到 NotificationListener 时,被拦截,就无法再向上传到 Scrollbar 中的监听。也就是说 Scrollbar 不起作用了。

Flutter 的滑动体系中通过 Notification 的分发与监听,让我们可以在任何地方去监听组件的滑动。这样滑动事件的得到了极大地解耦。至于滑动通知的具体流程,不是一言半语能够介绍完的。作为普通的使用者,了解到这样就已足够。我的第四本小册 《Flutter 滑动探索 - 珠联璧合》 中将会全面分析 Flutter 滑动体系的源码实现,敬请期待。
转载自:https://juejin.cn/post/6991290527433900063

