02.25 Flutter 之 Notification

學習最好的方式就是多用幾次,我們就從簡單的通知使用開始學習。在此之前,先看一下通知監聽的源碼:

<code>/// 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);  ...}/<code>

從註釋中可以瞭解到:

  • NotificationListener 是一個監聽向上冒泡的 Notification(通知)的 Widget。
  • 當通知來臨,onNotification 方法回調將會被觸發,前提是來臨的通知是 T 或者 T 的子類型。
  • 用 Notification.dispatch 方法可以發送一個通知。

來一個簡單的例子:

<code>Scaffold(        body: NotificationListener(            onNotification: (notification) {              switch (notification.runtimeType) {                case ScrollStartNotification:                  print("ScrollStartNotification");                  break;                case ScrollUpdateNotification:                  print("ScrollUpdateNotification");                  break;                case ScrollEndNotification:                  print("ScrollEndNotification");                  break;                case OverscrollNotification:                  print("OverscrollNotification");                  break;              }              return false;            },            child: ListView.builder(              itemBuilder: (BuildContext context, int index) {                return ListTile(                  title: Text("$index"),                );              },              itemCount: 40,            )))/<code>

當滑動 ListView 時會看到如下輸出:

<code>flutter: ScrollStartNotificationflutter: ScrollUpdateNotification...flutter: ScrollEndNotification/<code>

此處 NotificationListener 我們沒有限制類型,只要是通知,都會被監聽到,稍微改一下:

<code>//指定監聽通知的類型為滾動結束通知(ScrollEndNotification)NotificationListener<scrollendnotification>(   onNotification: (notification) {              switch (notification.runtimeType) {                case ScrollStartNotification:                  print("ScrollStartNotification");                  break;                case ScrollUpdateNotification:                  print("ScrollUpdateNotification");                  break;                case ScrollEndNotification:                  print("ScrollEndNotification");                  break;                case OverscrollNotification:                  print("OverscrollNotification");                  break;              }              return false;            },    child: ListView.builder(      itemCount: 100,      itemBuilder: (context, index) {        return ListTile(title: Text("$index"),);      }  ),);/<scrollendnotification>/<code>

此時只會打印 ScrollEndNotification,因為 NotificationListener 只監聽 ScrollEndNotification 類型的通知。

還可以看到 NotificationListener 的回調 onNotification 需要一個 bool 類型的返回值,其源碼:

<code>typedef NotificationListenerCallback 
= bool Function(T notification);/<code>

它的返回值類型為布爾值,當返回值為 true 時,阻止冒泡,其父級 Widget 將再也收不到該通知;當返回值為 false 時繼續向上冒泡通知。如果覺得難以理解可以這麼想,當返回 true 的時候,代表到我這裡通知已經被我完全消費了,所以通知傳遞不到上一層,返回 false 的時候,代表我沒有消費它,只是監聽到它,所以通知得以往上冒泡傳遞。

可以用下面下自定義通知案例證明 返回值 的作用。

自定義通知

自定義通知,只需要繼承自 Notification 類就行。

<code>class TestNotification extends Notification {  final String msg;  TestNotification(this.msg);}/<code>

發送通知,之前提到 Notification.dispatch 可以發送通知。完整的案例如下:

<code>import 'package:flutter/material.dart';class TestNotification extends Notification {  final String msg;  TestNotification(this.msg);}class TestNotificationPage extends StatefulWidget {  @override  _TestNotificationPageState createState() => _TestNotificationPageState();}class _TestNotificationPageState extends State<testnotificationpage> {  String msg = "";  @override  Widget build(BuildContext context) {    return Scaffold(      body: NotificationListener<testnotification>(        onNotification: (notification) {          print(notification.msg);          //能否打印取決於子監聽中的onNotification返回值          return true;        },        child: NotificationListener<testnotification>(            onNotification: (notification) {              setState(() {                msg += notification.msg + "\\t";              });              //這裡返回true,父節點的監聽將收不到通知              //返回false則可以              return false;            },            child: Container(              margin: EdgeInsets.only(top: 40),              alignment: Alignment.center,              child: Column(                children: <widget>[                  RaisedButton(                    //按鈕點擊時分發通知                    onPressed: () =>                        TestNotification("Hello").dispatch(context),                    child: Text("無效的通知"),                  ),                  Builder(                    builder: (context) {                      return RaisedButton(                        //按鈕點擊時分發通知                        onPressed: () =>                            TestNotification("Hello").dispatch(context),                        child: Text("發送通知"),                      );                    },                  ),                  Text(msg)                ],              ),            )),      ),    );  }}/<widget>/<testnotification>/<testnotification>/<testnotificationpage>/<code>

以上案例,只要我們點擊按鈕,就會發送一個 TestNotification 類型的通知,被監聽到顯示在界面。但是點擊“無效的通知”按鈕時,界面並沒有任何反應,只是因為我們此時傳入的 context 是 build(BuildContext context)中的 context,和 NotificationListener 是同一級的,因此 NotificationListener 監聽不到通知。套一個 Builder 組件 或者直接創建一個新的 Widget 都行。

當子層 NotificationListener 的 onNotification 返回 true 時,父 NotificationListener 接受不到通知,反之可以接受。

運行效果:

Flutter 之 Notification

從源碼更深入理解 Notification

要想更深入瞭解 why? 就得從源碼入手。通知完整流程是 發送通知===>冒泡傳遞通知===>監聽消費通知。我們就看源碼是怎麼實現這一流程的。

通知的發送

通知發送是在 Notification 類裡發送的,我們就先看一下 Notification 類:

Notification
<code>/// A notification that can bubble up the widget tree./// 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].///一個可以在widget樹上冒泡傳遞的通知///用你想要發送通知的實例調用dispatch方法可以發送一個通知,///通知將會被傳遞到NotificationListener監聽的widget樹中abstract class Notification {  @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;  }  //開始發送通知  void dispatch(BuildContext target) {    //    target?.visitAncestorElements(visitAncestor);  } ...}/<notification>/<code>

發送通知時,調用 context 中的 visitAncestorElements 方法,visitAncestorElements 在 Element 類中的實現如下:

<code>@override  void visitAncestorElements(bool visitor(Element element)) {    assert(_debugCheckStateIsActiveForAncestorLookup());    Element ancestor = _parent;    while (ancestor != null && visitor(ancestor))      //把父節點賦值給當前節點      ancestor = ancestor._parent;  }/<code>

visitAncestorElements 需要傳入一個回調方法,只要回調方法返回 true,它就會把父節點賦值給當前節點,達到向上遍歷傳遞的效果。

<code>target?.visitAncestorElements(visitAncestor);/<code>

傳入的是 visitAncestor,我們看 visitAncestor 方法如下:

<code>   @protected  @mustCallSuper  bool visitAncestor(Element element) {    //判斷當前element對應的Widget是否是NotificationListener。    //由於NotificationListener是繼承自StatelessWidget,    //故先判斷是否是StatelessElement    if (element is StatelessElement) {      final StatelessWidget widget = element.widget;      //是StatelessElement,則獲取element對應的Widget,判斷      //是否是NotificationListener 。      if (widget is NotificationListener<notification>) {        //是NotificationListener,則調用該NotificationListener的_dispatch方法        if (widget._dispatch(this, element))          return false;      }    }    //默認返回true,這使得visitAncestorElements會    //一直像父節點遍歷傳遞    return true;  }/<notification>/<code> 

可見,最終會調到 NotificationListener 中的_dispatch 方法,如果_dispatch 方法返回 true,則終止向上遍歷。返回 false 則繼續像父節點遍歷。

NotificationListener

監聽通知

<code>//onNotification回調typedef NotificationListenerCallback = bool Function(T notification);class NotificationListener extends StatelessWidget {  /// Creates a widget that listens for notifications.  const NotificationListener({    Key key,    @required this.child,    this.onNotification,  }) : super(key: key);  final NotificationListenerCallback onNotification;  bool _dispatch(Notification notification, Element element) {    // 如果通知監聽器不為空,並且當前通知類型是該NotificationListener    // 監聽的通知類型,則調用當前NotificationListener的onNotification    if (onNotification != null && notification is T) {      //調用onNotification回調      final bool result = onNotification(notification);       // 返回值決定是否繼續向上遍歷      return result == true;    }    return false;  }}/<code>

我們可以看到 NotificationListener 的 onNotification 回調最終是在_dispatch 方法中執行的,然後會根據返回值來確定是否繼續向上冒泡。

小結

通知是一套自底向上的消息傳遞機制。
  1. 調用 dispatch(context)發送通知,dispatch 會調用 context.visitAncestorElements(visitor) 方法。
  2. context.visitAncestorElements(visitor) 方法會從當前節點向上遍歷父節點,通過傳入的 visitor 方法回調調用 NotificationListener 的_dispatch 方法。
  3. _dispatch 方法會過濾一些通知類型,最後調用 onNotification 回調。
  4. 根據 onNotification 回調的返回值判斷通知是否繼續向上傳遞。

額外

NestedScrollView 與 ListView 共用時,當 ListView 需要傳入自己的 ScrollController 時,NestedScrollView 不會跟著滑動。此時我們只要把 ScrollController 換成 NotificationListener 即可。

效果

Flutter 之 Notification


分享到:


相關文章: