Flutter中的事件處理
無論是在Android中還是iOS中,都是有事件響應的。其主要是通過手指進行觸摸,當手指接觸到屏幕后,便開始進行事件響應了。
基本概念:指針事件
在Flutter的原始事件模型中,在手指接觸屏幕發起接觸事件時,flutter會首先確定手指與屏幕發生接觸的位置上究竟有哪些組件。然后通過命中測試(Hit Test)交給最內層的組件去響應。換句話說,也就是先從渲染樹的最底層的根的位置向上遍歷,直到遍歷到根結點位置。
flutter中的主要的事件響應分為三個部分組成 PointerDownEvent
、 PointerMoveEvent
、 PointerUpEvent
三個事件,分別是手指下落事件,手指移動事件,手指抬起事件。PointerEvent
是Flutter的原始指針事件的基礎類。主要返回有
- position:全局坐標的偏移量
- delta:兩次指針移動事件的距離
- pressure:按壓力度,如果手機不支持,始終返回1
- orientation:指針移動方向,是一個角度值
對於原始指針事件的監聽,Flutter提供了一個Listener類。這個類可以用它監聽包裹的子組件的原始指針事件。
Listener(
onPointerDown:(downPointEvent){},
onPointerMove:(movePointEvent){},
onPointerUp:(upPointEvent){},
behavior:HitTestBehavior,
child: Widget
)
behavior是決定子組件如何響應命中測試,值類型是HitTestBehavior,是一個枚舉類型。主要的取值有
- deferToChild:子組件一個接一個地命中測試,如果子組件中有命中測試的,那么當前組件會收到指針事件,並且父組件也會收到指針事件。
- opaque:在進行命中測試時,當前組件會被當成不透明進行處理,單擊的響應區域即為單擊區域。
- translucent:組件自身和底部可視區域都能夠響應命中測試,當點擊頂部組件時,頂部組件和底部組件都可以接收到指針事件。
忽略事件
兩個組件 AbsorbPointer
, IgnorePointer
編號 | 組件名稱 | 組件說明 |
---|---|---|
1 | AbsorbPointer | 其包裹的組件不能夠響應事件,但是其本身能夠響應指針事件 |
2 | IgnorePointer | 包裹的組件以及其本身都不能夠響應指針事件 |
手勢識別:GestureDetector
如果想從組件層監聽手勢,可以使用GestureDetector等手勢響應組件。該組件可以監聽各種觸摸的行為,常用的事件如下:
編號 | 事件名稱 | 描述 |
---|---|---|
1 | onTapDown | 接觸屏幕時觸發 |
2 | onTapUp | 離開屏幕時觸發 |
3 | onTap | 點擊屏幕時觸發 |
4 | onTapCancel | 觸發onTapDown事件但不會觸發onTap事件 |
5 | onDoubleTap | 用戶連續兩次在同一位置快讀點擊屏幕 |
6 | onLongPress | 在相同位置與屏幕保持長時間接觸 |
7 | onVerticalDragStart | 與屏幕接觸並可能開始垂直移動 |
8 | onVerticalDragUpdate | 與屏幕接觸並沿垂直方向移動 |
9 | onVerticalDragEnd | 之前與屏幕接觸並垂直移動的指針不再與屏幕接觸 |
10 | onHorizontalDragStart | 與屏幕接觸並可能開始水平移動 |
11 | onHorizontalDragUpdate | 與屏幕接觸並已沿水平方向移動 |
12 | onHorizontalDragEnd | 之前與屏幕接觸並水平移動的指針不再與屏幕接觸 |
如果同時監測onTap和onDoubleTap,那么在onTap后有200ms的延遲。
GestureDetector之所以能夠識別各種手勢,是因為其內部使用了一個或者多個GestureRecognizer手勢識別器。在使用手勢識別器后,需要調用 dispose()
進行資源的釋放,否則會造成大量的資源消耗。
手勢競爭與沖突
在flutter中,引入了手勢競技場的概念,用來識別究竟是哪個手勢最終響應用戶事件。該 手勢競技場 通過綜合對比用戶觸摸屏幕的時長、位移、拖拽方向來確定最終的手勢。換句話說,如果在屏幕上拖拽一個小球,那么該小球就會通過手勢競技場進行判斷移動的方向。一般只有單一方向,垂直移動或者水平移動。
事件總線
事件總線是廣播機制的一種實現方式(廣播為跨頁面事件通信提供了有效的解決方案)。訂閱者模式中包含兩種角色:發布者和訂閱者。在Flutter中:
- 發布者主要負責在狀態改變時通知所有的訂閱者
- 觀察者則負責訂閱事件並對接收到的事件進行處理。
使用事件總線可以實現組件之間狀態的共享,但是對於復雜場景來說,可以使用專門的管理框架例如:redux、ScopeModel或者Provider
事件通知
如果在一個比較復雜的頁面進行數據傳遞,那么使用到 事件通知 這個機制,在子節點跨層級傳遞消息機制。在Flutter的組件樹中,每一個節點都可以發送通知,通知會沿着當前節點向上傳遞,父節點則使用NotificationListener監聽子節點傳遞的消息,這種機制稱為 通知冒泡 。
通知冒泡 和 用戶觸摸事件冒泡 不太一樣,通知冒泡可以中止,用戶觸摸事件冒泡不可以中止。
自定義通知
如果需要實現自定義通知,需要實現一個類並且繼承 Notification 。在Notification類中,有一個dispatch() 可以用這個類進行分發事件通知。
冒泡通知的原理
因為通知 Notification 是通過dispatch進行觸發的,因此查看dispatch的源碼,如下:
void dispatch(BuildContext? target) {
// The `target` may be null if the subtree the notification is supposed to be
// dispatched in is in the process of being disposed.
target?.visitAncestorElements(visitAncestor);
}
可以知道,其是調用了visitAncestorElements方法,從當前元素開始向上遍歷父元素。當遍歷到根元素或者遍歷回調返回false時,遍歷過程中止。該冒泡通知的原理是一套自底向上的消息傳遞機制。