Flutter 中的事件基本上分為兩個層次:原始指標事件(Raw pointer events)與手勢(Gestures)。事件的觸發與否依 behavior 特性設定有關,不過,對於從 Web 前端開發過來的人而言,這些行為是有些陌生的,因為他們可能想要事件浮昇之類的行為。
在 Flutter 中提供了 Notification,元件可以發送(dispatch)通知(Notification),通知會沿著樹浮昇(bubbling up),父裔中的 NotificationListener 可以獲得通知,預設情況下,通知預設會持續往上浮昇,然而父裔中某個 NotificationListener 可以阻止通知浮昇。
例如,若想模擬一下 Web 中 DOM 的 click 事件,可以自訂一個 ClickNotification:
import 'package:flutter/material.dart';
// 自訂 NotificationListener
class ClickNotification extends Notification {
final Widget target;
ClickNotification({this.target});
}
void main() => runApp(
MaterialApp(
home: Scaffold(
// 監聽子裔的 ClickNotification
body: NotificationListener<ClickNotification> (
child: Center(
// 監聽 NotificationButton 的 ClickNotification
child: NotificationListener<ClickNotification> (
child: NotificationButton('Click me'),
onNotification: (notification) {
var button = notification.target as NotificationButton;
print('"${button.data}" Clicked');
// return true; // 傳回 true 可阻止通知浮昇
}
)
),
onNotification: (notification) {
print('bubble up');
}
),
),
)
);
class NotificationButton extends StatelessWidget {
final String data;
NotificationButton(this.data);
@override
Widget build(BuildContext context) {
return NotificationListener<ClickNotification>(
child: Builder(
builder: (context) {
return RawMaterialButton(
child: Text(data),
onPressed: () {
ClickNotification(target: this).dispatch(context);
},
);
},
),
);
}
}
在這個範例中,ClickNotification 繼承了 Notification,主要是用來包裹 target,也就是觸發通知的對象,在 Scaffold 的 body 部份,NotificationListener 會監聽子元件是否有通知,在其子裔中有個 NotificationButton,會在點選按鈕時發送 ClickNotification。
NotificationButton 的部份,主要將是用 NotificationListener 作為 Builder 的父元件,而在 RawMaterialButton 被按下時,onPressed 執行時,會建立 ClickNotification 並發送。
為什麼要用 Builder,而不是將 RawMaterialButton 實例傳回?主要是在 context,如果你使用以下:
...
class NotificationButton extends StatelessWidget {
final String data;
@override
Widget build(BuildContext context) {
return NotificationListener<ClickNotification>(
child: RawMaterialButton(
child: Text(data),
onPressed: () {
ClickNotification(target: this).dispatch(context);
},
),
);
}
}
那麼這時的 context 是 NotificationButton 的 Element,也就是說,這時 NotificationListener 監聽的子裔,會是從 NotificationButton 以上發出的 ClickNotification,這時作為 NotificationButton 的子元件 RawMaterialButton 發出的 ClickNotification,是不會有作用的。
至於原先指定 Builder 的方式,builder 設定的函式上 context 參數,接收到的是 Builder 對應的 Element,而 Builder 是 NotificationListener 的子元件,因此發送 ClickNotification 時,Builder 以上的父裔,預設可以收到通知。
範例若按下按鈕,主控台會顯示「“Click me” Clicked」與「bubble up」文字,如果將範例中的 return true 處註解移除,表示阻止通知浮昇,這時就只會顯示「“Click me” Clicked」。
實際上,Flutter 內建了一些 Notification 的實作,像是 DraggableScrollableNotification、KeepAliveNotification、LayoutChangedNotification(子類還有 ScrollNotification、SizeChangedLayoutNotification)、OverscrollIndicatorNotification 等,可以用來傾聽一些系統內建的通知,必要時也可以利用一下。

