引言
- 参考文献: 《Flutter 实践》(作者杜文)- 8.4 小节 Notification 。这个系列的文章都很不错,推荐!
- 熟悉 通知(Notification) 的同学请直接看冒泡原理小节。
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()
方法。
这里注意下:
如果父节点是 NotificationListener 且 _dispatch() 方法返回 true,则 visitor() 方法返回的是 false,中断循环遍历(可以看上一节代码跳出遍历循环的条件)。
如果父节点不是 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 结构图
总结下
xxxNotification
表示自定义的通知
1、xxxNotification.dispatch(context)
分发通知的时候就是调用 context.visitAncestorElements(visitor)
方法。
2、context.visitAncestorElements()
方法用来从当前节点向上遍历父节点,找到一个 NotificationLister
类型的控件,然后调用它的 onNotification()
回调方法。
3、回调方法的入参是来自子节点分发过来的通知 (xxxNotification),回调方法的返回值用来判断是否要拦截通知 (xxxNotification)。
4、一层层的向上找 NotificationLister
类型父节点并分发通知 (xxxNotification);若不拦截则继续向上寻找,直到根节点为止。这就是我们说的冒泡通知原理了。
5、最后再次感谢《Flutter实践》系列文章!