引言
- 參考文獻: 《Flutter 實踐》(作者杜文)- 8.4 小節 Notification 。這個系列的文章都很不錯,推薦!
- 熟悉 通知(Notification) 的同學請直接看冒泡原理小節。
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()
方法。
這裡注意下:
如果是 NotificationListener,且 _dispatch 方法返回 true,則 visitor 方法返回 false,中斷迴圈(上一節程式碼跳出迴圈條件)。
如果父節點不是 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);
);
複製程式碼
結構圖
總結下
xxxNotification
表示自定義的通知
1、xxxNotification.dispatch(context)
分發通知的時候就是呼叫 context.visitAncestorElements(visitor)
方法。
2、context.visitAncestorElements()
方法用來從當前節點向上遍歷父節點,找到一個 NotificationLister
型別的控制元件,然後呼叫它的 onNotification()
回撥方法。
3、回撥方法的入參是來自子節點分發過來的通知 (xxxNotification),回撥方法的返回值用來判斷是否要攔截通知 (xxxNotification)。
4、一層層的向上找 NotificationLister
型別父節點並分發通知 (xxxNotification);若不攔截則繼續向上尋找,直到根節點為止。這就是我們說的冒泡通知原理了。
5、最後再次感謝《Flutter實踐》系列文章!