1. 引子
在研究 ScrollView
原始碼時,有個很有意思的收穫。這裡作為引子,來引入 NotificationListener
元件。下面是 ScrollView#build
原始碼中的一部分,可以看出,當 keyboardDismissBehavior
為 onDrag
時,所構建的元件上層會巢狀一個 NotificationListener
元件,並在 onNotification
中進行邏輯性處理。
其中 ScrollViewKeyboardDismissBehavior
是隻有兩個元素的列舉。
enum ScrollViewKeyboardDismissBehavior {
manual,
onDrag,
}
複製程式碼
ListView
繼承自ScrollView
,構造中的 keyboardDismissBehavior
引數,會為 ScrollView
中定義的該成員進行初始化。測試的核心程式碼如下:
manual
和 onDrag
的效果如下:當前 鍵盤彈出時
,如果為 manual
,列表滑動過程中鍵盤不會主動隱藏
。為 onDrag
時,滑動列表時,鍵盤會主動隱藏
。
manual | onDrag |
---|---|
通過原始碼中的一個小細節
處理,我們能夠清楚地認識到 NotificationListener
價值。它可以監聽滑動的過程,回撥出相關的資料讓使用者進行邏輯處理
。
2. 認識 NotificationListener
首先 NotificationListener
是一個 StatelessWidget
,接受一個 Notification 族
泛型,構造方法中必須傳入一個 child
元件,可以設定 onNotification
的監聽。
onNotification
成員的型別為 NotificationListenerCallback<T>
,可以看出它是一個函式型別,返回 bool 值。入參為 T
泛型物件,且必須是 Notification
子類 。也就是說,該函式會回撥出一個資料,並且返回一個用於控制某個邏輯的標識。
---->[NotificationListener#onNotification 宣告]----
final NotificationListenerCallback<T>? onNotification;
typedef NotificationListenerCallback<T extends Notification> =
bool Function(T notification);
複製程式碼
既然作為一個 StatelessWidget
,那最重要的當屬 build
方法。 但從原始碼中可以看出,該元件直接返回使用者傳入的 child
,也就是說,它並不關心元件的構建邏輯。
最後,該類中還有一個私有方法 _dispatch
,該方法中需要傳入 Notification
物件,可以看出,這裡是使用者傳入的 onNotification
方法觸發場合。
3.認識 Notification
上面涉及到了很多處 Notification
,如果不瞭解這個類,那麼很難對 NotificationListener
有全面的認識。Notification
是一個 抽象類
,它沒有繼承任何類。其中有兩個普通方法 visitAncestor
和 dispatch
。
既然 Notification
是抽象類,那麼並不能直接構造物件,所以 Flutter
框架中自然要提供相關的實現類,如下是 Notification
的眾多實現類,包括引子中的 ScrollUpdateNotification
。這樣我們就知道能監聽到哪些 Notification
。
4.認識 NotificationListener 的使用
比如下面我們通過 NotificationListener
監聽 ScrollUpdateNotification
,這樣滑動時_onNotification
回撥就可以回撥出 notification
滑動資料。我們就可以根據這個物件進行相關邏輯處理。
class ListViewDemo extends StatelessWidget {
const ListViewDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return NotificationListener<ScrollUpdateNotification>(
onNotification: _onNotification,
child: ListView(
children: List.generate(
60,
(index) => ItemBox(index: index, )).toList()),
);
}
bool _onNotification(ScrollUpdateNotification notification) {
print('====dragDetails:${notification.dragDetails}'
'====pixels:${notification.metrics.pixels}');
return false;
}
}
複製程式碼
測試條目單體如下,使用 ItemBox
元件進行展示。
class ItemBox extends StatelessWidget {
final int index;
const ItemBox({Key? key, required this.index}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1 / window.devicePixelRatio,
))),
height: 56,
child: Text(
'第 $index 個',
style: TextStyle(fontSize: 20),
),
);
}
}
複製程式碼
我們可以監聽任意的 Notification
型別,比如下面的 OverscrollNotification
,這個監聽將會在列表滑動到最頂端或最底端時被觸發,在回撥的資料中可以得到越界的尺寸 overscroll
。
class ListViewDemo extends StatelessWidget {
const ListViewDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return NotificationListener<OverscrollNotification>(
onNotification: _onNotification,
child: ListView(
children: List.generate(
60,
(index) => ItemBox( index: index,)).toList()),
);
}
bool _onNotification(OverscrollNotification notification) {
print('====dragDetails:${notification.dragDetails}'
'====pixels:${notification.metrics.pixels}'
'=====overscroll:${notification.overscroll}');
return false;
}
}
複製程式碼
除了對某一種 Notification
監聽,我們也可以通過父級來監聽多個 Notification
,下面是監聽頂層抽象 Notification
類的程式碼。簡單上滑後日志如下,可以看出,這樣能夠同時監聽到多種型別的 Notification
通知,我們可以通過型別判斷來進行區分。
class ListViewDemo extends StatelessWidget {
const ListViewDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return NotificationListener<Notification>(
onNotification: _onNotification,
child: ListView(
children: List.generate(
60,
(index) => ItemBox( index: index,)).toList()),
);
}
bool _onNotification(Notification notification) {
print('====Notification type:${notification.runtimeType}======');
return false;
}
}
複製程式碼
5. 原始碼中對 NotificationListener 的使用
最經典的當屬 Scrollbar
原始碼中對 NotificationListener
的使用,它監聽 ScrollNotification
的五種通知,通過 _handleScrollNotification
進行處理。
這樣只要在 ListView
外層巢狀一個 Scrollbar
,在滑動過程中右側就可以出現指示器。
Scrollbar(
child: ListView(
children: List.generate(
60,
(index) => ItemBox(index: index,)).toList()),
),
複製程式碼
另外 RefreshIndicator
元件內部也是基於監聽 ScrollNotification
和 OverscrollIndicatorNotification
通知進行實現的。
6. NotificationListener 監聽中返回值的作用
從原始碼中可以看出,當返回 false
則表示通知可以繼續向上層節點分發。反之也就意味著通知被截斷。
比如下面程式碼,將 NotificationListener
放在 Scrollbar
下方,監聽時返回 true
。這樣 ListView
的滑動事件向上分發時,到 NotificationListener
時,被攔截,就無法再向上傳到 Scrollbar
中的監聽。也就是說 Scrollbar
不起作用了。
Flutter
的滑動體系中通過 Notification
的分發與監聽,讓我們可以在任何地方去監聽元件的滑動。這樣滑動事件的得到了極大地解耦。至於滑動通知的具體流程,不是一言半語能夠介紹完的。作為普通的使用者,瞭解到這樣就已足夠。我的第四本小冊 《Flutter 滑動探索 - 珠聯璧合》
中將會全面分析 Flutter
滑動體系的原始碼實現,敬請期待。