Flutter 通知(Notification)冒泡原理

旋轉頭像發表於2019-09-19

引言

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 方法最終在 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 來包裹 UI 控制元件監聽通知,而 NotificationListener 是整合自 StatelessWidget 的控制元件。

  • 程式碼第 8 - 10 行,就是在確定方法入參 element 是否是一個 NotificationListener聯絡上一節程式碼,就是在遍歷父節點過程中(visitor方法),確定父節點是否是 NotificationListener,是則呼叫 _dispatch() 方法。

這裡注意下:

  1. 如果是 NotificationListener,且 _dispatch 方法返回 true,則 visitor 方法返回 false,中斷迴圈(上一節程式碼跳出迴圈條件)。

  2. 如果父節點不是 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 )的回撥。所以如果我們想攔截通知,這裡返回 true,整體的 _dispatch 方法就會返回 true,中斷向上的迴圈遍歷。
NotificationListener<xxxNotification>(
	onNotification: (notification) {
 		 return false or true; // true 攔截通知
  	 },
   	child:
   	
   		 xxxNotification(' Hello ').dispatch(context);
  	
);
複製程式碼

結構圖

Notification

總結下

xxxNotification 表示自定義的通知

1、xxxNotification.dispatch(context) 分發通知的時候就是呼叫 context.visitAncestorElements(visitor) 方法。

2、context.visitAncestorElements()方法用來從當前節點向上遍歷父節點,找到一個 NotificationLister 型別的控制元件,然後呼叫它的 onNotification() 回撥方法。

3、回撥方法的入參是來自子節點分發過來的通知 (xxxNotification),回撥方法的返回值用來判斷是否要攔截通知 (xxxNotification)。

4、一層層的向上找 NotificationLister型別父節點並分發通知 (xxxNotification);若不攔截則繼續向上尋找,直到根節點為止。這就是我們說的冒泡通知原理了。

5、最後再次感謝《Flutter實踐》系列文章!

相關文章