Flutter Notification 使用说明
概述
在Flutter进行界面开发时,我们经常会遇到数据传递的问题。由于Flutter采用节点树的方式组织页面,以致于一个普通页面的节点层级会很深。当我们需要在子节点向父节点传递一些信息时,我们不可能层层传递Listener,所以我们需要一种在子节点跨层级传递消息的方式。
所幸,Flutter的Notification为我们提供了这样的能力。
使用方法
创建Notification
class TestNotification extends Notification {
TestNotification({
@required this.count,
});
final int count;
}
我们在Notification
中定义我们要传递的信息,本例中,我们只传递一个Int型。
创建NotificationListener节点
NotificationListener
继承了StatelessWidget。我们在一个较高的父节点,使用NotificationListener
,就可以监听来自子节点的消息了。
class GrandParentWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: new Text('Notification Demo'),
),
body: NotificationListener(
child: ParentWidget(),
onNotification: (TestNotification n){
print('随机数:${n.count}');
return true;
},
),
);
}
}
发送Notification
class ButtonWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
child: Text("Click Me"),
onTap: () {
new TestNotification(count: new Random().nextInt(100))
.dispatch(context);
},
),
);
}
}
原理
Notification
我们先来看看Notification
的实现。Notification
的实现非常简单,阅读源码时实属福利。
/// A notification that can bubble up the widget tree.
///
/// You can determine the type of a notification using the `is` operator to
/// check the [runtimeType] of the notification.
///
/// 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].
abstract class Notification {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const Notification();
/// Applied to each ancestor of the [dispatch] target.
///
/// The [Notification] class implementation of this method dispatches the
/// given [Notification] to each ancestor [NotificationListener] widget.
///
/// Subclasses can override this to apply additional filtering or to update
/// the notification as it is bubbled (for example, increasing a `depth` field
/// for each ancestor of a particular type).
@protected
@mustCallSuper
bool visitAncestor(Element element) {
if (element is StatelessElement) {
final StatelessWidget widget = element.widget;
if (widget is NotificationListener) {
if (widget._dispatch(this, element)) // that function checks the type dynamically
return false;
}
}
return true;
}
/// Start bubbling this notification at the given build context.
///
/// The notification will be delivered to any [NotificationListener] widgets
/// with the appropriate type parameters that are ancestors of the given
/// [BuildContext]. If the [BuildContext] is null, the notification is not
/// dispatched.
void dispatch(BuildContext target) {
// The `target` may be null if the subtree the notification is supposed to be
// dispatched in is in the process of being disposed.
target?.visitAncestorElements(visitAncestor);
}
...
}
除了注释之外,Notification
的核心代码只有十行左右。主要包含了visitAncestor
和dispatch
两个方法。
我们在调用dispatch
后,会调用visitAncestorElements
。
/// Walks the ancestor chain, starting with the parent of this build context's
/// widget, invoking the argument for each ancestor. The callback is given a
/// reference to the ancestor widget's corresponding [Element] object. The
/// walk stops when it reaches the root widget or when the callback returns
/// false. The callback must not return null.
///
/// This is useful for inspecting the widget tree.
///
/// Calling this method is relatively expensive (O(N) in the depth of the tree).
///
/// This method should not be called from [State.deactivate] or [State.dispose]
/// because the element tree is no longer stable at that time. To refer to
/// an ancestor from one of those methods, save a reference to the ancestor
/// by calling [visitAncestorElements] in [State.didChangeDependencies].
void visitAncestorElements(bool visitor(Element element)) {
assert(_debugCheckStateIsActiveForAncestorLookup());
Element ancestor = _parent;
while (ancestor != null && visitor(ancestor))
ancestor = ancestor._parent;
}
visitAncestorElements
是framework.dart中的方法,从注释中我们可以比较容易理解,这个方法主要是Flutter为我们提供的Widget向上遍历的方法。在调用方法时,我们需要传入一个visitor方法,当visitor方法返回false时,遍历终止。
到这里,我们就明白了,Notification
的dispatch方法,其实是向上遍历,寻找符合条件的父节点,然后进行处理。接下来我们看下Notification
的visitor
。
/// Applied to each ancestor of the [dispatch] target.
///
/// The [Notification] class implementation of this method dispatches the
/// given [Notification] to each ancestor [NotificationListener] widget.
///
/// Subclasses can override this to apply additional filtering or to update
/// the notification as it is bubbled (for example, increasing a `depth` field
/// for each ancestor of a particular type).
@protected
@mustCallSuper
bool visitAncestor(Element element) {
if (element is StatelessElement) {
final StatelessWidget widget = element.widget;
if (widget is NotificationListener) {
if (widget._dispatch(this, element)) // that function checks the type dynamically
return false;
}
}
return true;
}
实现非常简单,就是判断element的widget是否为NotificationListener,然后进行分发。如果分发的返回true,则visitAncestor
返回false,遍历终止。
在使用时,我们可以重写visitAncestor方法,来修改遍历的检查判断。
NotificationListener
将下来,我们看一下NotificationListener
的实现。
/// 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 extends StatelessWidget {
/// Creates a widget that listens for notifications.
const NotificationListener({
Key key,
@required this.child,
this.onNotification,
}) : super(key: key);
/// The widget directly below this widget in the tree.
///
/// This is not necessarily the widget that dispatched the notification.
///
/// {@macro flutter.widgets.child}
final Widget child;
/// Called when a notification of the appropriate type arrives at this
/// location in the tree.
///
/// Return true to cancel the notification bubbling. Return false (or null) to
/// allow the notification to continue to be dispatched to further ancestors.
///
/// The notification's [Notification.visitAncestor] method is called for each
/// ancestor, and invokes this callback as appropriate.
///
/// Notifications vary in terms of when they are dispatched. There are two
/// main possibilities: dispatch between frames, and dispatch during layout.
///
/// For notifications that dispatch during layout, such as those that inherit
/// from [LayoutChangedNotification], it is too late to call [State.setState]
/// in response to the notification (as layout is currently happening in a
/// descendant, by definition, since notifications bubble up the tree). For
/// widgets that depend on layout, consider a [LayoutBuilder] instead.
final NotificationListenerCallback 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;
}
@override
Widget build(BuildContext context) => child;
}
我们可以看到,dispatch
方法,只是进行了一些简单的类型检查,然后就调用我们传入的notification
方法了。这里值得注意的是,只有当我们notification
返回true时,遍历才会终止。
以上就是Flutter中Notification的基本原理和使用方法。