1. 引子
在研究 ScrollView
源码时,有个很有意思的收获。这里作为引子,来引入 NotificationListener
组件。下面是 ScrollView#build
源码中的一部分,可以看出,当 keyboardDismissBehavior
为 onDrag
时,所构建的组件上层会嵌套一个 NotificationListener
组件,并在 onNotification
中进行逻辑性处理。
其中 ScrollViewKeyboardDismissBehavior
是只有两个元素的枚举。
enum ScrollViewKeyboardDismissBehavior {
manual,
onDrag,
}
ListView
继承自ScrollView
,构造中的 keyboardDismissBehavior
参数,会为 ScrollView
中定义的该成员进行初始化。测试的核心代码如下:
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
滑动体系的源码实现,敬请期待。