通知(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_listener.dart]spa
void dispatch(BuildContext target) {
target?.visitAncestorElements(visitAncestor);
}
複製代碼
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_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()
方法。
這裏注意下:
若是父節點是 NotificationListener 且 _dispatch() 方法返回 true,則 visitor() 方法返回的是 false,中斷循環遍歷(能夠看上一節代碼跳出遍歷循環的條件)。
若是父節點不是 NotificationListener 或者 _dispatch() 方法返回 false, 則 visitor() 方法返回 true,繼續向上循環遍歷父節點。
[./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;
}
複製代碼
onNotification()
方法,這個是咱們構建 NotificationListener 時傳入的用來監聽通知( Notification )的回調(NotificationListenerCallback)。因此若是咱們想要攔截通知,這裏的回調方法應該返回 true, 而後 _dispatch()
方法就會返回 true,從而中斷循環遍歷。NotificationListener<xxxNotification>(
onNotification: (notification) {
return false or true; // true 攔截通知
},
child:
xxxNotification(' Hello ').dispatch(context);
);
複製代碼
xxxNotification
表示自定義的通知
一、xxxNotification.dispatch(context)
分發通知的時候就是調用 context.visitAncestorElements(visitor)
方法。
二、context.visitAncestorElements()
方法用來從當前節點向上遍歷父節點,找到一個 NotificationLister
類型的控件,而後調用它的 onNotification()
回調方法。
三、回調方法的入參是來自子節點分發過來的通知 (xxxNotification),回調方法的返回值用來判斷是否要攔截通知 (xxxNotification)。
四、一層層的向上找 NotificationLister
類型父節點並分發通知 (xxxNotification);若不攔截則繼續向上尋找,直到根節點爲止。這就是咱們說的冒泡通知原理了。
五、最後再次感謝《Flutter實踐》系列文章!