likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 18
前言:

1. 引子

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

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

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

enum ScrollViewKeyboardDismissBehavior {
  manual,
  onDrag,
}

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

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

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

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

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


2. 认识 NotificationListener

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

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

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

---->[NotificationListener#onNotification 声明]----
final NotificationListenerCallback<T>? onNotification;

typedef NotificationListenerCallback<T extends Notification> = 
                                               bool Function(T notification);

既然作为一个 StatelessWidget,那最重要的当属 build 方法。 但从源码中可以看出,该组件直接返回使用者传入的 child ,也就是说,它并不关心组件的构建逻辑。

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


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

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


3.认识 Notification

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

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


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

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


4.认识 NotificationListener 的使用

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

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

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 组件进行展示。

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

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

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

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 通知,我们可以通过类型判断来进行区分。

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

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 进行处理。

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

这样只要在 ListView 外层嵌套一个 Scrollbar ,在滑动过程中右侧就可以出现指示器。

Scrollbar(
  child: ListView(
      children: List.generate(
          60,
          (index) => ItemBox(index: index,)).toList()),
),

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


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

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


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

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

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

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

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


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