学习最好的方式就是多用几次,我们就从简单的通知使用开始学习。在此之前,先看一下通知监听的源码:
/// 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 接受不到通知,反之可以接受。
运行效果:

从源码更深入理解 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 方法中执行的,然后会根据返回值来确定是否继续向上冒泡。
小结
通知是一套自底向上的消息传递机制。
-
调用 dispatch(context)发送通知,dispatch 会调用 context.visitAncestorElements(visitor) 方法。
-
context.visitAncestorElements(visitor) 方法会从当前节点向上遍历父节点,通过传入的 visitor 方法回调调用 NotificationListener 的_dispatch 方法。
-
_dispatch 方法会过滤一些通知类型,最后调用 onNotification 回调。
-
根据 onNotification 回调的返回值判断通知是否继续向上传递。
额外
最后
贴一张自己学习Flutter的公众号,感兴趣的小伙伴可以一起学习哦。。。

网友评论