Flutter 通知(Notification)冒泡原理

引言

Notification

通知(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.dispatch(context) 方法分發通知

[./notification_listener.dart]spa

void dispatch(BuildContext target) {
    target?.visitAncestorElements(visitAncestor);
  }
複製代碼
  • Notification.dispatch(context) 方法實際調用的是 context. visitAncestorElements() 方法,並傳入了一個 Function(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 類的方法

[./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() 方法。

這裏注意下:

  1. 若是父節點是 NotificationListener 且 _dispatch() 方法返回 true,則 visitor() 方法返回的是 false,中斷循環遍歷(能夠看上一節代碼跳出遍歷循環的條件)。

  2. 若是父節點不是 NotificationListener 或者 _dispatch() 方法返回 false, 則 visitor() 方法返回 true,繼續向上循環遍歷父節點。

三、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 )的回調(NotificationListenerCallback)。因此若是咱們想要攔截通知,這裏的回調方法應該返回 true, 而後 _dispatch() 方法就會返回 true,從而中斷循環遍歷。
NotificationListener<xxxNotification>(
	onNotification: (notification) {
 		 return false or true; // true 攔截通知
  	 },
   	child:
   	
   		 xxxNotification(' Hello ').dispatch(context);
  	
);
複製代碼

Notification & NotificationListener 結構圖

Notification

總結下

xxxNotification 表示自定義的通知

一、xxxNotification.dispatch(context) 分發通知的時候就是調用 context.visitAncestorElements(visitor) 方法。

二、context.visitAncestorElements()方法用來從當前節點向上遍歷父節點,找到一個 NotificationLister 類型的控件,而後調用它的 onNotification() 回調方法。

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

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

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

相關文章
相關標籤/搜索