8.6 通知 Notification | 《Flutter实战·第二版》
概念理解
通知(Notification)是Flutter中一个重要的机制,在widget树中,每一个节点都可以分发通知,通知会沿着当前节点向上传递,所有父节点都可以通过NotificationListener
来监听通知。
Flutter中将这种由子向父的传递通知的机制称为通知冒泡(Notification Bubbling)。
通知冒泡和用户触摸事件冒泡是相似的,但有一点不同:通知冒泡可以中止,但用户触摸事件不行。
- 子widget可以通过Notification类及其子类来发送通知
- 父Widget可以通过NotificationListener或NotificationListenerTree来监听并处理这些通知。
- 当子Widget发送通知时,Flutter会自动沿着Widget树向上冒泡,直到找到第一个处理该通知的父Widget为止。
- 如果没有能够处理该通知的父Widget,通知将会一直冒泡到根Widget(通常是MaterialApp或WidgetsApp),如果根Widget仍然无法处理该通知,则该通知将被忽略。
NotificationListener
NotificationListener
继承自StatelessWidget
类,所以它可以直接嵌套到 Widget 树中,它可以指定一个监听的模板T,该模板参数类型必须是继承自Notification。
指定后,只有当监听到模板后才会调用回调函数。
监听的模板T,就是子widget发出的t通知 Notification
class NotificationListener<T extends Notification> extends StatelessWidget {
const NotificationListener({
Key key,
required this.child,
this.onNotification,
}) : super(key: key);
...//省略无关代码
}
-
child,是被监听的子widget
-
onNotification,
回调为通知处理回调的函数方法
typedef NotificationListenerCallback<T extends Notification> = bool Function(T notification);
onNotification的返回值是一个bool型。
当返回值为true
时,阻止冒泡,其父级Widget将再也收不到该通知;当返回值为false
时继续向上冒泡通知。
Notification
发送通知
Notification
有一个dispatch(context)
方法,它是用于分发通知的,它要求一个BuildContext作为参数传入。
context
实际上就是操作Element
的一个接口,它与Element
树上的节点是对应的,通知会从context
对应的Element
节点向上冒泡。
void dispatch(BuildContext target) {
target?.visitAncestorElements(visitAncestor);
}
dispatch(context)
中调用了当前context的visitAncestorElements
方法,该方法会从当前Element开始向上遍历父级Element;visitAncestorElements
有一个遍历回调参数 visitAncestor,在遍历过程中对遍历到的父级Element都会执行该回调。- 遍历的终止条件是:已经遍历到根Element或某个遍历回调返回
false(此时返回的false是由
NotificationListener
的_dispatch
方法返回的)
。
//遍历回调,会对每一个父级Element执行此回调
bool visitAncestor(Element element) {
//判断当前element对应的Widget是否是NotificationListener。
//由于NotificationListener是继承自StatelessWidget,
//故先判断是否是StatelessElement
if (element is StatelessElement) {
//是StatelessElement,则获取element对应的Widget,判断
//是否是NotificationListener 。
final StatelessWidget widget = element.widget;
if (widget is NotificationListener<Notification>) {
//是NotificationListener,则调用该NotificationListener的_dispatch方法
if (widget._dispatch(this, element))
return false;
}
}
return true;
}
visitAncestor
会判断每一个遍历到的父级Widget是否是NotificationListener
,如果不是,则返回true
继续向上遍历,如果是,则调用NotificationListener
的_dispatch
方法
bool _dispatch(Notification notification, Element element) {
// 如果通知监听器不为空,并且当前通知类型是该NotificationListener
// 监听的通知类型,则调用当前NotificationListener的onNotification
if (onNotification != null && notification is T) {
final bool result = onNotification(notification);
// 返回值决定是否继续向上遍历
return result == true;
}
return false;
}
NotificationListener
的onNotification
回调最终也是在_
_dispatch
方法中执行的,执行条件是:通知监听器不为空且通知监听器的类型与该父widget所监听的类型相同。- 最后根据回调函数的返回值,判断是否向上继续冒泡
个人理解梳理:
子widget发出通知 Notification,父widget 通过 NotificationListener 进行监听,当子widget发出通知时,flutter会根据子widget所在的 BuildContext 的 Element 节点调用 visitAncestorElements
向上遍历父Element,每遍历一个父Element都会回调
visitAncestor 方法
因为,NotificationListener继承自 StatelessWidget ,所以判断当前 Element
是否为statelessElement ,如果是则继续判断其 Element
对应的widget是否为NotificationListener;
如果是,则调用 NotificationListener
的 _dispatch
方法 ,根据此处的返回值判断是否继续查看
如果不是,则返回true,继续向上调用检查。
自定义Notification
自定义notification
自定义notification时需要继承Notification
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class CustomNotification extends Notification {
final String data;
CustomNotification(this.data);
}
main
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'notification.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("初始定义"),
),
body: BodyPage(),
),
);
}
}
class BodyPage extends StatelessWidget {
List<String> datas = ["朱一龙", "肖战", "王一博", "白宇"];
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 500,
child: NotificationListener<CustomNotification>(
onNotification: (notification) {
print("监听的子widget是Center:${notification.data}");
return false;
},
child: Center(
child: NotificationListener<CustomNotification>(
onNotification: (notification) {
print("监听的子widget是Column:${notification.data}");
return false;
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("这是一些名字"),
Container(
height: 500,
width: 300,
color: Colors.amber,
child: ListView.builder(
itemCount: datas.length,
itemBuilder: (context, index) {
return Container(
height: 30,
child: Row(
children: [
Text("${datas[index]}"),
SizedBox(
width: 30,
),
ElevatedButton(
onPressed: () {
CustomNotification(datas[index])
.dispatch(context);
},
child: Text("分发"))
],
),
);
}),
)
],
),
),
),
),
),
);
}
}
此时有两层嵌套的 NotificationListener ,它们都是监听 CustomNotification 通知的。
- 当子NotificationListener 的 onNotification 回调返回了
false
,表示不阻止冒泡,所以父NotificationListener 仍然会受到通知,所以控制台会打印出通知信息;
- 当子NotificationListener 的 onNotification 回调的返回值改为
true
,则父NotificationListener便不会再打印通知了,因为子 NotificationListener 已经终止通知冒泡了。