美文网首页All in FlutterFlutter圈子Flutter
Flutter 之 Notification 深入学习

Flutter 之 Notification 深入学习

作者: 向日花开 | 来源:发表于2020-01-17 22:29 被阅读0次

学习最好的方式就是多用几次,我们就从简单的通知使用开始学习。在此之前,先看一下通知监听的源码:


/// A widget that listens for [Notification]s bubbling up the tree.
///
/// Notifications will trigger the [onNotification] callback only if their
/// [runtimeType] is a subtype of `T`.
///
/// To dispatch notifications, use the [Notification.dispatch] method.
class NotificationListener<T extends Notification> extends StatelessWidget {
  /// Creates a widget that listens for notifications.
  const NotificationListener({
    Key key,
    @required this.child,
    //回调方法
    this.onNotification,
  }) : super(key: key);

  ...
}

从注释中可以了解到:

  • NotificationListener 是一个监听向上冒泡的 Notification(通知)的 Widget。
  • 当通知来临,onNotification 方法回调将会被触发,前提是来临的通知是 T 或者 T 的子类型。

  • 用 Notification.dispatch 方法可以发送一个通知。

来一个简单的例子:

Scaffold(
        body: NotificationListener(
            onNotification: (notification) {
              switch (notification.runtimeType) {
                case ScrollStartNotification:
                  print("ScrollStartNotification");
                  break;
                case ScrollUpdateNotification:
                  print("ScrollUpdateNotification");
                  break;
                case ScrollEndNotification:
                  print("ScrollEndNotification");
                  break;
                case OverscrollNotification:
                  print("OverscrollNotification");
                  break;
              }
              return false;
            },
            child: ListView.builder(
              itemBuilder: (BuildContext context, int index) {
                return ListTile(
                  title: Text("$index"),
                );
              },
              itemCount: 40,
            )))

当滑动 ListView 时会看到如下输出:

flutter: ScrollStartNotification
flutter: ScrollUpdateNotification
...
flutter: ScrollEndNotification

此处 NotificationListener 我们没有限制类型,只要是通知,都会被监听到,稍微改一下:

//指定监听通知的类型为滚动结束通知(ScrollEndNotification)
NotificationListener<ScrollEndNotification>(
   onNotification: (notification) {
              switch (notification.runtimeType) {
                case ScrollStartNotification:
                  print("ScrollStartNotification");
                  break;
                case ScrollUpdateNotification:
                  print("ScrollUpdateNotification");
                  break;
                case ScrollEndNotification:
                  print("ScrollEndNotification");
                  break;
                case OverscrollNotification:
                  print("OverscrollNotification");
                  break;
              }
              return false;
            },
    child: ListView.builder(
      itemCount: 100,
      itemBuilder: (context, index) {
        return ListTile(title: Text("$index"),);
      }
  ),
);

此时只会打印 ScrollEndNotification,因为 NotificationListener 只监听 ScrollEndNotification 类型的通知。

还可以看到 NotificationListener 的回调 onNotification 需要一个 bool 类型的返回值,其源码:


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


它的返回值类型为布尔值,当返回值为 true 时,阻止冒泡,其父级 Widget 将再也收不到该通知;当返回值为 false 时继续向上冒泡通知。如果觉得难以理解可以这么想,当返回 true 的时候,代表到我这里通知已经被我完全消费了,所以通知传递不到上一层,返回 false 的时候,代表我没有消费它,只是监听到它,所以通知得以往上冒泡传递。

可以用下面下自定义通知案例证明 返回值 的作用。

自定义通知

自定义通知,只需要继承自 Notification 类就行。

class TestNotification extends Notification {
  final String msg;

  TestNotification(this.msg);
}

发送通知,之前提到 Notification.dispatch 可以发送通知。完整的案例如下:

import 'package:flutter/material.dart';

class TestNotification extends Notification {
  final String msg;

  TestNotification(this.msg);
}

class TestNotificationPage extends StatefulWidget {
  @override
  _TestNotificationPageState createState() => _TestNotificationPageState();
}

class _TestNotificationPageState extends State<TestNotificationPage> {
  String msg = "";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NotificationListener<TestNotification>(
        onNotification: (notification) {
          print(notification.msg);
          //能否打印取决于子监听中的onNotification返回值
          return true;
        },
        child: NotificationListener<TestNotification>(
            onNotification: (notification) {
              setState(() {
                msg += notification.msg + "\t";
              });
              //这里返回true,父节点的监听将收不到通知
              //返回false则可以
              return false;
            },
            child: Container(
              margin: EdgeInsets.only(top: 40),
              alignment: Alignment.center,
              child: Column(
                children: <Widget>[
                  RaisedButton(
                    //按钮点击时分发通知
                    onPressed: () =>
                        TestNotification("Hello").dispatch(context),
                    child: Text("无效的通知"),
                  ),
                  Builder(
                    builder: (context) {
                      return RaisedButton(
                        //按钮点击时分发通知
                        onPressed: () =>
                            TestNotification("Hello").dispatch(context),
                        child: Text("发送通知"),
                      );
                    },
                  ),
                  Text(msg)
                ],
              ),
            )),
      ),
    );
  }
}

以上案例,只要我们点击按钮,就会发送一个 TestNotification 类型的通知,被监听到显示在界面。但是点击“无效的通知”按钮时,界面并没有任何反应,只是因为我们此时传入的 context 是 build(BuildContext context)中的 context,和 NotificationListener 是同一级的,因此 NotificationListener 监听不到通知。套一个 Builder 组件 或者直接创建一个新的 Widget 都行。

当子层 NotificationListener 的 onNotification 返回 true 时,父 NotificationListener 接受不到通知,反之可以接受。

运行效果:


1.png

从源码更深入理解 Notification

要想更深入了解 why? 就得从源码入手。通知完整流程是 发送通知===>冒泡传递通知===>监听消费通知。我们就看源码是怎么实现这一流程的。

通知的发送

通知发送是在 Notification 类里发送的,我们就先看一下 Notification 类:

Notification
/// A notification that can bubble up the widget tree.

/// To listen for notifications in a subtree, use a [NotificationListener].
///
/// To send a notification, call [dispatch] on the notification you wish to
/// send. The notification will be delivered to any [NotificationListener]
/// widgets with the appropriate type parameters that are ancestors of the given
/// [BuildContext].
///一个可以在widget树上冒泡传递的通知
///用你想要发送通知的实例调用dispatch方法可以发送一个通知,
///通知将会被传递到NotificationListener监听的widget树中
abstract class Notification {

  @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;
  }

  //开始发送通知
  void dispatch(BuildContext target) {
    //
    target?.visitAncestorElements(visitAncestor);
  }
 ...
}

发送通知时,调用 context 中的 visitAncestorElements 方法,visitAncestorElements 在 Element 类中的实现如下:

@override
  void visitAncestorElements(bool visitor(Element element)) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    Element ancestor = _parent;
    while (ancestor != null && visitor(ancestor))
      //把父节点赋值给当前节点
      ancestor = ancestor._parent;
  }

visitAncestorElements 需要传入一个回调方法,只要回调方法返回 true,它就会把父节点赋值给当前节点,达到向上遍历传递的效果。

target?.visitAncestorElements(visitAncestor);

传入的是 visitAncestor,我们看 visitAncestor 方法如下:

   @protected
  @mustCallSuper
  bool visitAncestor(Element element) {
    //判断当前element对应的Widget是否是NotificationListener。
    //由于NotificationListener是继承自StatelessWidget,
    //故先判断是否是StatelessElement
    if (element is StatelessElement) {
      final StatelessWidget widget = element.widget;
      //是StatelessElement,则获取element对应的Widget,判断
      //是否是NotificationListener 。
      if (widget is NotificationListener<Notification>) {
        //是NotificationListener,则调用该NotificationListener的_dispatch方法
        if (widget._dispatch(this, element))
          return false;
      }
    }
    //默认返回true,这使得visitAncestorElements会
    //一直像父节点遍历传递
    return true;
  }

可见,最终会调到 NotificationListener 中的_dispatch 方法,如果_dispatch 方法返回 true,则终止向上遍历。返回 false 则继续像父节点遍历。

NotificationListener

监听通知


//onNotification回调
typedef NotificationListenerCallback<T extends Notification> = bool Function(T notification);


class NotificationListener<T extends Notification> extends StatelessWidget {
  /// Creates a widget that listens for notifications.
  const NotificationListener({
    Key key,
    @required this.child,
    this.onNotification,
  }) : super(key: key);

  final NotificationListenerCallback<T> onNotification;

  bool _dispatch(Notification notification, Element element) {
    // 如果通知监听器不为空,并且当前通知类型是该NotificationListener
    // 监听的通知类型,则调用当前NotificationListener的onNotification
    if (onNotification != null && notification is T) {

      //调用onNotification回调
      final bool result = onNotification(notification);

       // 返回值决定是否继续向上遍历
      return result == true;
    }
    return false;
  }
}

我们可以看到 NotificationListener 的 onNotification 回调最终是在_dispatch 方法中执行的,然后会根据返回值来确定是否继续向上冒泡。

小结

通知是一套自底向上的消息传递机制。
  1. 调用 dispatch(context)发送通知,dispatch 会调用 context.visitAncestorElements(visitor) 方法。

  2. context.visitAncestorElements(visitor) 方法会从当前节点向上遍历父节点,通过传入的 visitor 方法回调调用 NotificationListener 的_dispatch 方法。

  3. _dispatch 方法会过滤一些通知类型,最后调用 onNotification 回调。

  4. 根据 onNotification 回调的返回值判断通知是否继续向上传递。

额外

Notification解决滑动的问题

最后

贴一张自己学习Flutter的公众号,感兴趣的小伙伴可以一起学习哦。。。

相关文章

网友评论

    本文标题:Flutter 之 Notification 深入学习

    本文链接:https://www.haomeiwen.com/subject/uxsczctx.html