【Flutter 组件集录】NotificationListener| 8月更文挑战

2022-03-08 15:56:14 浏览数 (1)

1. 引子

在研究 ScrollView 源码时,有个很有意思的收获。这里作为引子,来引入 NotificationListener 组件。下面是 ScrollView#build 源码中的一部分,可以看出,当 keyboardDismissBehavioronDrag 时,所构建的组件上层会嵌套一个 NotificationListener 组件,并在 onNotification 中进行逻辑性处理。

其中 ScrollViewKeyboardDismissBehavior 是只有两个元素的枚举。

代码语言:javascript复制
enum ScrollViewKeyboardDismissBehavior {
  manual,
  onDrag,
}

ListView 继承自ScrollView ,构造中的 keyboardDismissBehavior 参数,会为 ScrollView 中定义的该成员进行初始化。测试的核心代码如下:

manualonDrag 的效果如下:当前 键盘弹出时,如果为 manual ,列表滑动过程中键盘不会主动隐藏 。为 onDrag 时,滑动列表时,键盘会主动隐藏

通过源码中的一个小细节处理,我们能够清楚地认识到 NotificationListener 价值。它可以监听滑动的过程,回调出相关的数据让使用者进行逻辑处理

2. 认识 NotificationListener

首先 NotificationListener 是一个 StatelessWidget ,接受一个 Notification 族 泛型,构造方法中必须传入一个 child 组件,可以设置 onNotification 的监听。

onNotification 成员的类型为 NotificationListenerCallback<T> ,可以看出它是一个函数类型,返回 bool 值。入参为 T 泛型对象,且必须是 Notification 子类 。也就是说,该函数会回调出一个数据,并且返回一个用于控制某个逻辑的标识。

代码语言:javascript复制
---->[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 是一个 抽象类 ,它没有继承任何类。其中有两个普通方法 visitAncestordispatch

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

4.认识 NotificationListener 的使用

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

代码语言:javascript复制
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 组件进行展示。

代码语言:javascript复制
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

代码语言:javascript复制
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 通知,我们可以通过类型判断来进行区分。

代码语言:javascript复制
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 ,在滑动过程中右侧就可以出现指示器。

代码语言:javascript复制
Scrollbar(
  child: ListView(
      children: List.generate(
          60,
          (index) => ItemBox(index: index,)).toList()),
),

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

6. NotificationListener 监听中返回值的作用

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

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

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

0 人点赞