Flutter Notification使用說明

  • 2020 年 2 月 25 日
  • 筆記

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<TestNotification>(          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<Notification>) {          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的核心程式碼只有十行左右。主要包含了visitAncestordispatch兩個方法。

我們在調用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方法,其實是向上遍歷,尋找符合條件的父節點,然後進行處理。接下來我們看下Notificationvisitor

 /// 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<Notification>) {          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<T extends Notification> 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<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;    }      @override    Widget build(BuildContext context) => child;  }

我們可以看到,dispatch方法,只是進行了一些簡單的類型檢查,然後就調用我們傳入的notification方法了。這裡值得注意的是,只有當我們notification返回true時,遍歷才會終止。

以上就是Flutter中Notification的基本原理和使用方法。