Flutter 通知(Notification)冒泡原理

4,573 阅读4分钟

引言

Notification

通知(Notification) 是 Flutter 中重要的机制,在 Widget 树中的任一节点都以分发通知,通知会沿着当前节点向上传递冒泡,其父节点都可以用 NotificationListener 来监听或拦截通知。

Flutter中很多地方使用了通知,如可滚动组件(Scrollable Widget)滑动时就会分发滚动通知(ScrollNotification),而Scrollbar正是通过监听ScrollNotification来确定滚动条位置的。

class NotificationPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NotificationListener<ScrollEndNotification>(
        onNotification: (notification){
          switch (notification.runtimeType){
            case ScrollStartNotification: print("开始滚动"); break;
            case ScrollUpdateNotification: print("正在滚动"); break;
            case ScrollEndNotification: print("滚动停止"); break;
            case OverscrollNotification: print("滚动到边界"); break;
            case UserScrollNotification: print("滚动到边界"); break;
            default:
              print(notification.runtimeType);
              break;
          }
          return true;
        },
        child: ListView.builder(
            itemCount: 20,
            itemBuilder: (_, index) {
              return ListTile(title: Text('$index'));
            }),
      ),
    );
  }
}

第 17 行,如果在 NotificationListener 的 onNotification 回调方法中返回 true,表示当前节点拦截通知消息,阻止向上传递;返回 false 表示不拦截。

自定义通知

class xxxNotification extends Notification {
  final String msg;

  xxxNotification(this.msg);
}

NotificationListener<xxxNotification>(
	onNotification: (notification) {
 		 return false or true;
  	 },
   	child:
   	 ...
   		 xxxNotification(' Hello ').dispatch(context);
  	 ...
);

  • 在需要监听通知的地方使用 NotificationListener(功能性 Widget) 包装 UI 控件,然后某个子控件使用 xxxNotification().dispatch(context) 方法发送通知。

  • Notification 的 dispatch 方法有我们想了解的冒泡原理。

通知冒泡原理

1、子节点使用 Notification.dispatch(context) 方法分发通知

[./notification_listener.dart]

  void dispatch(BuildContext target) {
    target?.visitAncestorElements(visitAncestor);
  }
  • Notification.dispatch(context) 方法实际调用的是 context. visitAncestorElements() 方法,并传入了一个 Function(bool visitor(Element element)) 参数。

[./framework.dart]

  @override
  void visitAncestorElements(bool visitor(Element element)) {
    // ...
    Element ancestor = _parent;
    while (ancestor != null && visitor(ancestor))
      ancestor = ancestor._parent;
  }
  • context.visitAncestorElements() 最终在 Element 类中找到对应的实现(至于 context - element 的关系有兴趣可再去研究)。

  • 代码第 4 - 6 行,典型的递归循环。从当前 element 的父节点开始,不断地往上遍历父节点,并调用 visitor() 方法检查父节点。

  • 跳出循环有两种情况:

    1.不存在父节点(ancestor == null):已经从当前 element 遍历到了根 element

    2.方法 visitor(ancestor) 返回 false :检查父节点的方法返回 false。

  • 冒泡原理关键点就在这个方法 bool visitor(Element element)了。

2、Notification 类的方法

[./notification_listener.dart]

  void dispatch(BuildContext target) {
    target?.visitAncestorElements(visitAncestor);
  }

  @protected
  @mustCallSuper
  bool visitAncestor(Element element) {
    if (element is StatelessElement) {
      final StatelessWidget widget = element.widget;
      if (widget is NotificationListener<Notification>) {
        if (widget._dispatch(this, element)) // that function checks the type dynamically
          return false;
      }
    }
    return true;
  }
  • 代码第 8 行,先判断 element 是否是 StatelessElement。这么做是因为我们一般会用 NotificationListener 来 包裹 child 控件以监听子节点分发的通知,而 NotificationListener 就是继承自 StatelessWidget 的控件。

  • 代码第 8 - 10 行,确定方法入参 element 是否是一个 NotificationListener联系上一节代码,可以看到通过 context 遍历父节点的过程中,用 visitor 方法来确定父节点是否是 NotificationListener,是,则调用它的 _dispatch() 方法。

这里注意下:

  1. 如果父节点是 NotificationListener 且 _dispatch() 方法返回 true,则 visitor() 方法返回的是 false,中断循环遍历(可以看上一节代码跳出遍历循环的条件)。

  2. 如果父节点不是 NotificationListener 或者 _dispatch() 方法返回 false, 则 visitor() 方法返回 true,继续向上循环遍历父节点。

3、NotificationListener 类 _dispatch 方法

[./notification_listener.dart]

final NotificationListenerCallback<T> onNotification;
 
bool _dispatch(Notification notification, Element element) {
    if (onNotification != null && notification is T) {
      final bool result = onNotification(notification);
      return result == true; // so that null and false have the same effect
    }
    return false;
  }
  • 代码第 5 行,调用 onNotification() 方法,这个是我们构建 NotificationListener 时传入的用来监听通知( Notification )的回调(NotificationListenerCallback)。所以如果我们想要拦截通知,这里的回调方法应该返回 true, 然后 _dispatch() 方法就会返回 true,从而中断循环遍历。
NotificationListener<xxxNotification>(
	onNotification: (notification) {
 		 return false or true; // true 拦截通知
  	 },
   	child:
   	
   		 xxxNotification(' Hello ').dispatch(context);
  	
);

Notification & NotificationListener 结构图

Notification

总结下

xxxNotification 表示自定义的通知

1、xxxNotification.dispatch(context) 分发通知的时候就是调用 context.visitAncestorElements(visitor) 方法。

2、context.visitAncestorElements()方法用来从当前节点向上遍历父节点,找到一个 NotificationLister 类型的控件,然后调用它的 onNotification() 回调方法。

3、回调方法的入参是来自子节点分发过来的通知 (xxxNotification),回调方法的返回值用来判断是否要拦截通知 (xxxNotification)。

4、一层层的向上找 NotificationLister类型父节点并分发通知 (xxxNotification);若不拦截则继续向上寻找,直到根节点为止。这就是我们说的冒泡通知原理了。

5、最后再次感谢《Flutter实践》系列文章!