通知(Notification) 是 Flutter 中重要的机制,在 Widget 树中的任一节点都以分发通知,通知会沿着当前节点向上传递冒泡,其父节点均可以用 NotificationListener
来监听或拦截通知。html
Flutter中不少地方使用了通知,如可滚动组件(Scrollable Widget)滑动时就会分发滚动通知(ScrollNotification),而Scrollbar正是经过监听ScrollNotification来肯定滚动条位置的。less
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 表示不拦截。ide
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)
方法发送通知。ui
Notification 的 dispatch
方法有咱们想了解的冒泡原理。this
[./notification_listener.dart]spa
void dispatch(BuildContext target) {
target?.visitAncestorElements(visitAncestor);
}
复制代码
bool visitor(Element element)
) 参数。[./framework.dart]code
@override
void visitAncestorElements(bool visitor(Element element)) {
// ...
Element ancestor = _parent;
while (ancestor != null && visitor(ancestor))
ancestor = ancestor._parent;
}
复制代码
context.visitAncestorElements() 最终在 Element 类中找到对应的实现(至于 context - element 的关系有兴趣可再去研究)。cdn
代码第 4 - 6 行,典型的递归循环。从当前 element 的父节点开始,不断地往上遍历父节点,并调用 visitor()
方法检查父节点。htm
跳出循环有两种状况:blog
1.不存在父节点(ancestor == null):已经从当前 element 遍历到了根 element
2.方法
visitor(ancestor)
返回false
:检查父节点的方法返回 false。
冒泡原理关键点就在这个方法 bool visitor(Element element)
了。
[./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,继续向上循环遍历父节点。
[./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;
}
复制代码
onNotification()
方法,这个是咱们构建 NotificationListener 时传入的用来监听通知( Notification )的回调(NotificationListenerCallback)。因此若是咱们想要拦截通知,这里的回调方法应该返回 true, 而后 _dispatch()
方法就会返回 true,从而中断循环遍历。NotificationListener<xxxNotification>(
onNotification: (notification) {
return false or true; // true 拦截通知
},
child:
xxxNotification(' Hello ').dispatch(context);
);
复制代码
xxxNotification
表示自定义的通知
一、xxxNotification.dispatch(context)
分发通知的时候就是调用 context.visitAncestorElements(visitor)
方法。
二、context.visitAncestorElements()
方法用来从当前节点向上遍历父节点,找到一个 NotificationLister
类型的控件,而后调用它的 onNotification()
回调方法。
三、回调方法的入参是来自子节点分发过来的通知 (xxxNotification),回调方法的返回值用来判断是否要拦截通知 (xxxNotification)。
四、一层层的向上找 NotificationLister
类型父节点并分发通知 (xxxNotification);若不拦截则继续向上寻找,直到根节点为止。这就是咱们说的冒泡通知原理了。
五、最后再次感谢《Flutter实践》系列文章!