開篇
flutter的觸控事件涉及到的東西比較多,本篇文章將會從 GestureDetector
作為切入點,來對觸控事件的實現做一個全面的瞭解
GestureDetector
class GestureDetector extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
...
return RawGestureDetector(
gestures: gestures,
behavior: behavior,
excludeFromSemantics: excludeFromSemantics,
child: child,
);
}
}
複製程式碼
這裡,會將所有在 GestureDetector
設定的各種點選事件,放在各個 GestureRecognizer
中,然後通過 GestureRecognizerFactory
對其進行封裝,存入 gestures
中,這裡 GestureRecognizerFactory
的實現還是蠻有意思的,感興趣的小夥伴可以去看一下它的原始碼
這裡,我們先簡單瞭解一下 GestureRecognizer
GestureRecognizer
abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableTreeMixin {
...
void addPointer(PointerDownEvent event) {
...
if (isPointerAllowed(event)) {
addAllowedPointer(event);
} else {
handleNonAllowedPointer(event);
}
}
@protected
void addAllowedPointer(PointerDownEvent event) { }
...
}
複製程式碼
GestureRecognizer
主要提供了addPointer(downEvent)
用於接收 PointerDownEvent
物件,它會呼叫 addAllowedPointer(event)
方法,由子類去具體實現
GestureRecognizer
繼承於 GestureArenaMember
,它只提供了兩個方法
abstract class GestureArenaMember {
void acceptGesture(int pointer);
void rejectGesture(int pointer);
}
複製程式碼
分別表示手勢競技場成員獲勝與失敗時會呼叫的方法,接下來我們來看一下 RawGestureDetector
RawGestureDetector
class RawGestureDetector extends StatefulWidget {
...
}
class RawGestureDetectorState extends State<RawGestureDetector> {
...
void _handlePointerDown(PointerDownEvent event) {
assert(_recognizers != null);
for (final GestureRecognizer recognizer in _recognizers.values)
recognizer.addPointer(event);
}
。。。
@override
Widget build(BuildContext context) {
Widget result = Listener(
onPointerDown: _handlePointerDown,
behavior: widget.behavior ?? _defaultBehavior,
child: widget.child,
);
if (!widget.excludeFromSemantics)
result = _GestureSemantics(...);
return result;
}
...
}
複製程式碼
RawGestureDetector
是一個 StatefulWidget
,它預設返回的是 Listener
物件。其中還有一個 _GestureSemantics
它用於實現一些具有語義化的手勢,比如長按展示 tooltip
等,這裡我們不用關注它。
這裡將之前通過 GestureDetector
傳入的 gestures
轉換成了 _recognizers
,並且將他們放在了 _handlePointerDown(event)
方法裡通過 onPointerDown
傳給了 Listener
物件
這裡還需要注意, _handlePointerDown(event)
中對 GestureRecognizer
物件進行了遍歷,並呼叫了他們的 addPointer(event)
方法
接下來我們看一下 Listener
Listener
class Listener extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
Widget result = _child;
if (onPointerEnter != null ||
onPointerExit != null ||
onPointerHover != null) {
result = MouseRegion(...);
}
result = _PointerListener(
onPointerDown: onPointerDown,
onPointerUp: onPointerUp,
onPointerMove: onPointerMove,
onPointerCancel: onPointerCancel,
onPointerSignal: onPointerSignal,
behavior: behavior,
child: result,
);
return result;
}
}
複製程式碼
Listener
是一個 StatelessWidget
,當傳入的部分事件不為 null 時,比如懸停事件 onPointerHover
,返回的就是 MouseRegion
,它用來處理滑鼠的輸入,預設返回 _PointerListener
,這裡我們只需要關注這個物件
從前面我們可以知道,我們只對 Listener
傳入了 onPointerDown
,所以這裡傳遞給 _PointerListener
的其他手勢回撥都是 null
_PointerListener
class _PointerListener extends SingleChildRenderObjectWidget {
...
@override
RenderPointerListener createRenderObject(BuildContext context) {
return RenderPointerListener(
onPointerDown: onPointerDown,
...
);
}
...
}
複製程式碼
_PointerListener
是一個 SingleChildRenderObjectWidget
,它對應的 RenderObject
是 RenderPointerListener
,我們來看一下這個物件
RenderPointerListener
class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
...
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
...
if (onPointerDown != null && event is PointerDownEvent)
return onPointerDown(event);
...
}
}
複製程式碼
之前我們分析 RenderObject
時就知道, RenderObject
物件有一個 HitTestTarget
介面
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget { ... }
abstract class HitTestTarget {
factory HitTestTarget._() => null;
void handleEvent(PointerEvent event, HitTestEntry entry);
}
複製程式碼
這個介面提供了 handleEvent(...)
方法供 RenderObject
實現去處理各種手勢事件,最終基本上都是交給子類去實現這個方法。這裡的 RenderPointerListener
就是如此
這裡對於 GestureDetector
的整體結構有了一個初步的瞭解,並且無法再往下深入了,接下來我們將從另外一個切入點來看手勢事件。那就是手勢分發的起點
手勢分發流程
起點其實不難找,之前我們就知道過了,runApp(...)
方法作為flutter的入口,會對 WidgetsFlutterBinding
進行初始化,WidgetsFlutterBinding
混入了多個 Binding
物件,其中就有專門處理手勢的 GestureBinding
,我們看一下就知道了
手勢分發起點: GestureBinding
在 GestureBinding
的 initInstances()
中可以看到如下內容
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
@override
void initInstances() {
super.initInstances();
_instance = this;
window.onPointerDataPacket = _handlePointerDataPacket;
}
...
}
複製程式碼
這裡的 _handlePointerDataPacket
就是觸控事件的起點,它會處理裝置輸入的資訊,將其轉換為flutter中的手勢事件
_handlePointerDataPacket(...)
final Queue<PointerEvent> _pendingPointerEvents = Queue<PointerEvent>();
void _handlePointerDataPacket(ui.PointerDataPacket packet) {
_pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
if (!locked)
_flushPointerEventQueue();
}
複製程式碼
手勢事件都被存放在了一個佇列中,之後會呼叫 _flushPointerEventQueue()
來進行手勢分發
_flushPointerEventQueue(...)
void _flushPointerEventQueue() {
assert(!locked);
while (_pendingPointerEvents.isNotEmpty)
_handlePointerEvent(_pendingPointerEvents.removeFirst());
}
複製程式碼
這裡通過遍歷佇列,呼叫 _handlePointerEvent(event)
對各個event進行處理
_handlePointerEvent(event)
void _handlePointerEvent(PointerEvent event) {
...
HitTestResult hitTestResult;
if (event is PointerDownEvent || event is PointerSignalEvent) {
...
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
...
} else if (event.down) {
...
}
...
if (hitTestResult != null ||
event is PointerHoverEvent ||
event is PointerAddedEvent ||
event is PointerRemovedEvent) {
dispatchEvent(event, hitTestResult);
}
}
複製程式碼
當你在在螢幕上點選一下,觸發的事件流程如下:
PointerHoverEvent -> PointerDownEvent -> PointerUpEvent
在螢幕上滑動時,流程如下
PointerHoverEvent -> PointerDownEvent -> ...PointerMoveEvent... -> PointerUpEvent
PointerHoverEvent
主要用於 flutter_web 中的滑鼠懸停事件,這裡我們不關注它,我們可以看一下,當觸發 PointerDownEvent
時,做了些什麼
final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};
...
if (event is PointerDownEvent || event is PointerSignalEvent) {
assert(!_hitTests.containsKey(event.pointer));
hitTestResult = HitTestResult();
hitTest(hitTestResult, event.position);
if (event is PointerDownEvent) {
_hitTests[event.pointer] = hitTestResult;
}
...
}
...
@override // from HitTestable
void hitTest(HitTestResult result, Offset position) {
result.add(HitTestEntry(this));
}
複製程式碼
這裡的 hitTest(...)
由介面 HitTestable
提供,值得注意的是 GestureBinding
與 RendererBinding
都實現了這個介面,可以看一下在 RendererBinding
中的實現
///RendererBinding
@override
void hitTest(HitTestResult result, Offset position) {
assert(renderView != null);
renderView.hitTest(result, position: position);
super.hitTest(result, position);
}
複製程式碼
這裡呼叫了 RenderView
的 hitTest(...)
方法,我們已經知道過了,它是根 RenderObject
物件,進入它的 hitTest(...)
看一下
///RenderView
bool hitTest(HitTestResult result, { Offset position }) {
if (child != null)
child.hitTest(BoxHitTestResult.wrap(result), position: position);
result.add(HitTestEntry(this));
return true;
}
複製程式碼
這裡的 child
是 RenderBox
物件,在 RenderBox
中預設提供了 hitTest(...)
這個方法
///RenderBox
bool hitTest(BoxHitTestResult result, { @required Offset position }) {
...
if (_size.contains(position)) {
if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
result.add(BoxHitTestEntry(this, position));
return true;
}
}
return false;
}
複製程式碼
所以看到這裡你就能知道,當觸發了 PointerDownEvent
事件時,會呼叫所有 RenderBox
的 hitTest(...)
方法,將符合條件的物件放入到 BoxHitTestEntry(...)
中,再存入 HitTestResult
維護的 List<HitTestEntry> _path
裡
HitTestEntry
接受的物件是 HitTestTarget
,上面我們也提到過了,RenderObject
是實現了這個介面的。所以最後這些 RenderBox
會被存入 BoxHitTestEntry
先放入 List<HitTestEntry>
中,其次是存入了 HitTestEntry
的 RenderView
,最後才是 GestureBinding
物件
至於為什麼要把這些物件收集起來放入 HitTestResult
呢?後面會逐步說明
當 HitTestResult
被建立後,會被存入GestureBinding
維護的 Map<int, HitTestResult> _hitTests
中,key 是 event.pointer
,每觸發一次事件,pointer
的值都會+1,不會重複
接下來,會進入 dispatchEvent(event, hitTestResult)
方法,進行分發事件
dispatchEvent(...)
@override // from HitTestDispatcher
void dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {
...
if (hitTestResult == null) {
assert(event is PointerHoverEvent || event is PointerAddedEvent || event is PointerRemovedEvent);
try {
pointerRouter.route(event);
} catch (exception, stack) {
...
return;
}
for (final HitTestEntry entry in hitTestResult.path) {
try {
entry.target.handleEvent(event.transformed(entry.transform), entry);
}
...
}
}
複製程式碼
我們主要關注 entry.target.handleEvent(...)
方法,這裡對之前在 hitTest(...)
中新增的各個實現了 HitTestTarget
介面的物件,呼叫其 handleEvent(...)
方法。而 hitTestResult.path
的順序我們已經說過了,大致是下面這樣:
... -> RenderPointerListener -> ... -> RenderView -> GestureBinding
這裡會依次呼叫他們實現的 handleEvent(...)
。而事件的分發,就是通過這樣實現的!
開始手勢分發
我們再次進入 RenderPointerListener
的 handleEvent(...)
方法
RenderPointerListener
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
...
if (onPointerDown != null && event is PointerDownEvent)
return onPointerDown(event);
...
}
複製程式碼
這裡會呼叫 onPointerDown(event)
,通過前面的的瞭解,我們知道這個方法就是 RawGestureDetectorState
傳入的 _handlePointerDown(...)
,再來看一遍
void _handlePointerDown(PointerDownEvent event) {
for (final GestureRecognizer recognizer in _recognizers.values)
recognizer.addPointer(event);
}
複製程式碼
addPointer(event)
的內容之前也說過了,最終都是呼叫 GestureRecognizer
提供的 addAllowedPointer(...)
方法。如果我們在 GestureDetector
中設定了 onTapDown()
或者其他點選事件,這裡就會呼叫 TapGestureRecognizer
的 addPointer(...)
方法。我們就先以它為例,來看一下都做了些什麼
TapGestureRecognizer
我們先簡單的看一下 TapGestureRecognizer
的繼承結構
TapGestureRecognizer -> BaseTapGestureRecognizer -> PrimaryPointerGestureRecognizer -> OneSequenceGestureRecognizer -> GestureRecognizer
這些類中,只有 GestureRecognizer
實現了 addPointer(...)
方法,只有 BaseTapGestureRecognizer
和 PrimaryPointerGestureRecognizer
實現了 addAllowedPointer(...)
方法
可以先來看一下 BaseTapGestureRecognizer
的 addAllowedPointer(...)
BaseTapGestureRecognizer
PointerDownEvent _down;
...
@override
void addAllowedPointer(PointerDownEvent event) {
assert(event != null);
if (state == GestureRecognizerState.ready) {
_down = event;
}
if (_down != null) {
super.addAllowedPointer(event);
}
}
複製程式碼
這裡只是做了一個簡單的賦值,儲存傳遞的 PointerDownEvent
,它會在 _reset()
中被置空
接下來,進入 PrimaryPointerGestureRecognizer
的 addAllowedPointer(...)
PrimaryPointerGestureRecognizer
@override
void addAllowedPointer(PointerDownEvent event) {
startTrackingPointer(event.pointer, event.transform);
if (state == GestureRecognizerState.ready) {
state = GestureRecognizerState.possible;
primaryPointer = event.pointer;
initialPosition = OffsetPair(local: event.localPosition, global: event.position);
if (deadline != null)
_timer = Timer(deadline, () => didExceedDeadlineWithEvent(event));
}
}
複製程式碼
這裡主要關注 startTrackingPointer(...)
方法,它在 OneSequenceGestureRecognizer
中實現
OneSequenceGestureRecognizer
@protected
void startTrackingPointer(int pointer, [Matrix4 transform]) {
GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent, transform);
_trackedPointers.add(pointer);
assert(!_entries.containsValue(pointer));
_entries[pointer] = _addPointerToArena(pointer);
}
複製程式碼
上面有兩個比較關鍵的地方,先看第一個 addRoute(...)
///PointerRouter
final Map<int, Map<PointerRoute, Matrix4>> _routeMap = <int, Map<PointerRoute, Matrix4>>{};
...
void addRoute(int pointer, PointerRoute route, [Matrix4 transform]) {
final Map<PointerRoute, Matrix4> routes = _routeMap.putIfAbsent(
pointer,
() => <PointerRoute, Matrix4>{},
);
assert(!routes.containsKey(route));
routes[route] = transform;
}
複製程式碼
在這裡把 handleEvent(...)
方法作為 PointerRoute
傳入到了 PointerRouter
中,它的作用,我們後面就知道了。接下來看另一個非常關鍵的地方:_entries[pointer] = _addPointerToArena(pointer)
GestureArenaTeam _team;
...
GestureArenaEntry _addPointerToArena(int pointer) {
...
return GestureBinding.instance.gestureArena.add(pointer, this);
}
複製程式碼
可以看到,這裡將當前的 OneSequenceGestureRecognizer
作為 GestureArenaMember
物件,傳入了 GestureBinding
中維護的 GestureArenaManager
內。也就是將需要競技的手勢成員,放入了手勢競技場內。
那麼到這裡,RenderPointerListener
的 handleEvent(...)
就執行完畢了,接下來會執行 RenderView
的 handleEvent(...)
,不過由於它並沒有重寫這個方法,所以我們會直接來到 GestureBinding
的 handleEvent(...)
GestureBinding
final PointerRouter pointerRouter = PointerRouter();
...
@override // from HitTestTarget
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event);
if (event is PointerDownEvent) {
gestureArena.close(event.pointer);
} else if (event is PointerUpEvent) {
gestureArena.sweep(event.pointer);
} else if (event is PointerSignalEvent) {
pointerSignalResolver.resolve(event);
}
}
複製程式碼
這裡就要進入這個非常重要的 route(event)
方法了
PointerRouter
final Map<int, Map<PointerRoute, Matrix4>> _routeMap = <int, Map<PointerRoute, Matrix4>>{};
final Map<PointerRoute, Matrix4> _globalRoutes = <PointerRoute, Matrix4>{};
void route(PointerEvent event) {
final Map<PointerRoute, Matrix4> routes = _routeMap[event.pointer];
final Map<PointerRoute, Matrix4> copiedGlobalRoutes = Map<PointerRoute, Matrix4>.from(_globalRoutes);
if (routes != null) {
_dispatchEventToRoutes(
event,
routes,
Map<PointerRoute, Matrix4>.from(routes),
);
}
_dispatchEventToRoutes(event, _globalRoutes, copiedGlobalRoutes);
}
複製程式碼
這裡有兩個 _dispatchEventToRoutes(...)
方法,後者執行的是 _globalRoutes
中的 PointerRoute
,而 _globalRoutes
中存放的是其他的全域性手勢,比如用於隱藏 ToolTips
的手勢等,目前還不太瞭解它的其他具體作用。
不過前面的 routes
就是我們之前在 OneSequenceGestureRecognizer
的 startTrackingPointer(...)
中新增的各個 handleEvent(...)
方法
可以看一下 _dispatchEventToRoutes(...)
void _dispatchEventToRoutes(
PointerEvent event,
Map<PointerRoute, Matrix4> referenceRoutes,
Map<PointerRoute, Matrix4> copiedRoutes,
) {
copiedRoutes.forEach((PointerRoute route, Matrix4 transform) {
if (referenceRoutes.containsKey(route)) {
_dispatch(event, route, transform);
}
});
}
...
void _dispatch(PointerEvent event, PointerRoute route, Matrix4 transform) {
try {
event = event.transformed(transform);
route(event);
}
...
}
複製程式碼
就是遍歷然後執行所有的 PointerRoute
方法,這裡的 PointerRoute
就是之前的各個 OneSequenceGestureRecognizer
中的 handleEvent(...)
這裡要注意不要把
OneSequenceGestureRecognizer
的handleEvent(...)
和RenderPointerListener
的handleEvent(...)
混淆了
OneSequenceGestureRecognizer
提供了 handleEvent(...)
,交由子類去實現,我們接著之前的點選事件,實現它的點選子類是 PrimaryPointerGestureRecognizer
;如果是拖拽事件的話,實現的子類就是 DragGestureRecognizer
PrimaryPointerGestureRecognizer
@override
void handleEvent(PointerEvent event) {
assert(state != GestureRecognizerState.ready);
if (state == GestureRecognizerState.possible && event.pointer == primaryPointer) {
final bool isPreAcceptSlopPastTolerance = ...;
final bool isPostAcceptSlopPastTolerance = ...;
if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
resolve(GestureDisposition.rejected);
stopTrackingPointer(primaryPointer);
} else {
handlePrimaryPointer(event);
}
}
stopTrackingIfPointerNoLongerDown(event);
}
@protected
void handlePrimaryPointer(PointerEvent event);
複製程式碼
當前事件是 PointerMoveEvent
時,會做一個判斷,如果一定時間內滑動的距離超過 18px 那麼就會進入第一個判斷中,該手勢會被拒絕掉,也就是從競技場中會被移除,此時會觸發 onTapCancel
我們主要關心手勢有效時的方法 handlePrimaryPointer(event)
,可以看到這個方法是交由子類去實現的,實現它的子類自然就是 BaseTapGestureRecognizer
了
BaseTapGestureRecognizer
@override
void handlePrimaryPointer(PointerEvent event) {
if (event is PointerUpEvent) {
_up = event;
_checkUp();
} else if (event is PointerCancelEvent) {
resolve(GestureDisposition.rejected);
...
_reset();
} else if (event.buttons != _down.buttons) {
resolve(GestureDisposition.rejected);
stopTrackingPointer(primaryPointer);
}
}
複製程式碼
可以看到,在這裡只對 PointerUpEvent
與 PointerCancelEvent
進行了處理,並沒有處理 PointerDownEvent
,這裡很自然的就可以知道, PointerDownEvent
肯定被放在了 GestureBinding
的 handleEvent(...)
的後面部分進行處理
不過我們這裡還是可以先看一下 PointerUpEvent
是如何處理的,進入 _checkUp()
方法
@protected
void handleTapUp({ PointerDownEvent down, PointerUpEvent up });
...
void _checkUp() {
...
handleTapUp(down: _down, up: _up);
_reset();
}
複製程式碼
_checkUp()
中呼叫了 handleTapUp(...)
方法,它是一個交由子類實現的方法,而 BaseTapGestureRecognizer
的子類就是 TapGestureRecognizer
了
TapGestureRecognizer
@protected
T invokeCallback<T>(String name, RecognizerCallback<T> callback, { String debugReport() }) {
...
result = callback();
...
return result;
}
@protected
@override
void handleTapUp({PointerDownEvent down, PointerUpEvent up}) {
final TapUpDetails details = TapUpDetails(...);
switch (down.buttons) {
case kPrimaryButton:
if (onTapUp != null)
invokeCallback<void>('onTapUp', () => onTapUp(details));
if (onTap != null)
invokeCallback<void>('onTap', onTap);
break;
...
default:
}
}
複製程式碼
可以看到,最終通過 invokeCallback(...)
方法執行了傳入的方法,包括 onTapUp
和 onTap
,從這裡我們就知道了,我們最最常用的點選事件,就是在 TapGestureRecognizer
的 handleTapUp(...)
中執行的。
而 TapGestureRecognizer
還有 handleTapDown(...)
用於執行 onTapDown
,它則是通過 BaseTapGestureRecognizer
的 _checkDown()
呼叫
接下來我們可以回到 GestureBinding
,看看 PointerDownEvent
到底是如何處理的
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event);
if (event is PointerDownEvent) {
gestureArena.close(event.pointer);
}
...
}
複製程式碼
GestureArenaManager -> close(...)
void close(int pointer) {
final _GestureArena state = _arenas[pointer];
...
state.isOpen = false;
...
_tryToResolveArena(pointer, state);
}
void _tryToResolveArena(int pointer, _GestureArena state) {
assert(_arenas[pointer] == state);
assert(!state.isOpen);
if (state.members.length == 1) {
scheduleMicrotask(() => _resolveByDefault(pointer, state));
} else if (state.members.isEmpty) {
_arenas.remove(pointer);
assert(_debugLogDiagnostic(pointer, 'Arena empty.'));
} else if (state.eagerWinner != null) {
assert(_debugLogDiagnostic(pointer, 'Eager winner: ${state.eagerWinner}'));
_resolveInFavorOf(pointer, state, state.eagerWinner);
}
}
複製程式碼
close(...)
呼叫了 _tryToResolveArena(...)
方法,在這個方法中,處理了三種情況
- 如果競技場成員只有一個
- 如果競技場沒有任何成員
- 如果存在競技場的獲勝者
如果成員只有一個,那麼事件理所應當交給它處理;如果沒有成員就不說了;如果存在勝利者,交給勝利者處理也是正常的。顯然,close(...)
中並沒有對競技場的成員做一個競爭的處理,它只負責沒有點選衝突的時候,也就是隻有一個點選物件。這種情況最後會通過呼叫它的 acceptGesture(...)
來觸發 onTapDown
我們繼續看 close(...)
後面的方法
@override // from HitTestTarget
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event);
if (event is PointerDownEvent) {
gestureArena.close(event.pointer);
} else if (event is PointerUpEvent) {
gestureArena.sweep(event.pointer);
}
...
}
複製程式碼
GestureArenaManager -> sweep(...)
void sweep(int pointer) {
...
if (state.members.isNotEmpty) {
...
state.members.first.acceptGesture(pointer);
...
for (int i = 1; i < state.members.length; i++)
state.members[i].rejectGesture(pointer);
}
}
複製程式碼
看看這個方法,多麼簡單粗暴,直接確定競技場成員中的第一個為獲勝者!根據前面我們說過的 hintTest(...)
的新增順序,可以知道,RenderBox
數最下層的物件是最先被新增到列表中的
所以這裡的第一個成員,就是最下層的物件,在螢幕的顯示中,它就是最裡層的元素,所以如果像下面這樣,為兩個顏色塊設定點選事件的話,只有紅色的會生效
我們可以簡單的看一下 acceptGesture(...)
做了些什麼,這裡會進入 BaseTapGestureRecognizer
的 acceptGesture(...)
///BaseTapGestureRecognizer
@override
void acceptGesture(int pointer) {
super.acceptGesture(pointer);
if (pointer == primaryPointer) {
_checkDown();
_wonArenaForPrimaryPointer = true;
_checkUp();
}
}
複製程式碼
可以看到,這裡的 _checkDown()
中就處理了 onTapDown
事件,後面跟著了一個 _checkUp()
用於對 onTapUp
和 onTap
的處理,如果競技場成員只有一個,這裡的 _checkUp()
不會生效
到這裡,關於點選事件的整個流程我們都清楚了,可以分為下面兩種情況
- 競技場只有一個成員時:在
GestureArenaManager
的close(...)
中完成onTapDown
的呼叫,此時事件為PointDownEvent
; 在PointerRouter
的route(...)
方法完成對onTapUp
和onTap
的呼叫,此時事件為PointUpEvent
- 競技場有多個成員時:在
GestureArenaManager
的sweep(...)
方法先完成對onTapDown
的呼叫,後完成對onTapUp
與onTap
的呼叫,此時事件為PointUpEvent
接下來可能你會產生疑問了,如果上面兩個顏色塊監聽的是相同的滑動事件,在競技場中他們又是如何處理的呢?
下面就來簡單的看一下,以 PanGestureRecognizer
為例
PanGestureRecognizer
簡單的看一下它的結構
PanGestureRecognizer -> DragGestureRecognizer -> OneSequenceGestureRecognizer -> GestureRecognizer
基本上和拖拽相關的核心邏輯都在 DragGestureRecognizer
中了
從之前我們的流程就知道,會先走 addAllowedPointer(...)
方法,之後通過在 GestureBinding
的 handleEvent(...)
中執行 PointerRouter
的 route(...)
來走 GestureRecognizer
的 handleEvent(...)
方法
所以我們先看 DragGestureRecognizer
的 addAllowedPointer(...)
@override
void addAllowedPointer(PointerEvent event) {
startTrackingPointer(event.pointer, event.transform);
_velocityTrackers[event.pointer] = VelocityTracker();
if (_state == _DragState.ready) {
_state = _DragState.possible;
...
_checkDown();
} else if (_state == _DragState.accepted) {
resolve(GestureDisposition.accepted);
}
}
複製程式碼
還是會先在 startTrackingPointer(...)
將 handleEvent(...)
加入 PointerRouter
,然後把當前物件加入競技場。
接著通過 _checkDown()
執行了 onDown
方法,對於 DragGestureRecognizer
它就是 onPanDown
以上是收到 onPointDownEvent
時的事件,因為是拖拽,接下來會收到 onPointMoveEvent
事件
再看它的 handleEvent(...)
@override
void handleEvent(PointerEvent event) {
...
if (event is PointerMoveEvent) {
...
if (_state == _DragState.accepted) {
...
} else {
...
if (_hasSufficientGlobalDistanceToAccept)
resolve(GestureDisposition.accepted);
}
}
...
}
複製程式碼
其中 _hasSufficientGlobalDistanceToAccept
是交由子類去實現的方法,用於判斷滑動距離是否有效,預設大於36個畫素就有效;如果有效,就會進入 resolve(...)
方法,它最終會呼叫到 GestureArenaManager
的 _resolveInFavorOf(...)
而這個方法,對於傳入的 GestureArenaMember
物件,直接判定其為勝出者,並將其他競技場成員清除掉。而這裡傳入的物件和我們之前再點選事件中的一樣,都是樹結構中最下層的物件,也就是螢幕上最裡層的元素
所以這裡的滑動事件在競技場中的處理就是這樣了。而我們本篇的內容也即將結束
總結
手勢的分發流程大致如下:
- 觸發手勢: flutter接受到由底層傳來的觸控事件通知,它會觸發
GestureBinding
的_handlePointerDataPacket(...)
方法,flutter再這個方法中對傳來的資料進行轉換,變成flutter中適用的格式 - HitTestTarget物件收集: 通過
hitTest(...)
方法,將RenderObject
樹中符合條件的RenderBox
物件新增到HitTestResult
中,新增順序是由底至上的,最上層的兩個物件分別是GestureBinding
與RenderView
,這些物件都實現了HitTestTarget
介面,也就是說他們都具備handlerEvent(...)
方法 - 事件分發: 在
dispatchEvent(...)
中,通過遍歷之前新增的物件,呼叫他們的handlerEvent(...)
方法來進行事件的分發 - GestureRecognizer物件收集: 我們的
GestureDetector
對應的RenderPointerListener
會進行事件處理,在收到PointDownEvent
事件時,會將所有GestureDetector
中註冊的GestureRecognizer
物件的handlerEvent
方法作為PointerRoute
傳入PointerRouter
,並將該物件放入GestureArenaManager
維護的競技場 - 事件處理: 執行到最外層,也就是
GestureBinding
的handleEvent
中,在這裡從競技場選出最終處理手勢的GestureRecognizer
物件,然後進行手勢處理
ps:以上分析都是基於單點觸控事件,尚未對多點觸控事件進行分析
最後,說明一下,單點觸控事件的核心並不是所謂的手勢競技場,因為根本就沒有一個真正的競技過程。最終都是直接選擇最下層的 GestureDetector
作為手勢的處理者,單點觸控事件的核心其實是這些競技場成員被新增到競技場中的順序——是由底至上的順序