在使用点击事件时,我们使用GestureDetector嵌套子组件的方式。那么点击事件背后的TapGestureRecognizer如何识别出用户在“点击”。
从GestureDetector源码build方法中可以看到,在我们给GestureDetector组件添加了onTap方法后,GestureDetector会添加TapGestureRecognizer 手势识别器,这个识别器主要功能就是识别点击事件。
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
- @override
- Widget build(BuildContext context) {
- final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
-
- if (onTapDown != null ||
- onTapUp != null ||
- onTap != null ||
- onTapCancel != null ||
- onSecondaryTap != null ||
- onSecondaryTapDown != null ||
- onSecondaryTapUp != null ||
- onSecondaryTapCancel != null||
- onTertiaryTapDown != null ||
- onTertiaryTapUp != null ||
- onTertiaryTapCancel != null
- ) {
- gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
- () => TapGestureRecognizer(debugOwner: this),
- (TapGestureRecognizer instance) {
- instance
- ..onTapDown = onTapDown
- ..onTapUp = onTapUp
- ..onTap = onTap
- ..onTapCancel = onTapCancel
- ..onSecondaryTap = onSecondaryTap
- ..onSecondaryTapDown = onSecondaryTapDown
- ..onSecondaryTapUp = onSecondaryTapUp
- ..onSecondaryTapCancel = onSecondaryTapCancel
- ..onTertiaryTapDown = onTertiaryTapDown
- ..onTertiaryTapUp = onTertiaryTapUp
- ..onTertiaryTapCancel = onTertiaryTapCancel;
- },
- );
- }
在根据设置的不同手势回调,底层添加不同的手势识别器,例如点击,长按,和双击。在最后使用
RawGestureDetector 组件封装手势识别器Map
- return RawGestureDetector(
- gestures: gestures,
- behavior: behavior,
- excludeFromSemantics: excludeFromSemantics,
- child: child,
- );
继续查看RawGestureDetector源码,在其build方法中我们找到Listener
- @override
- Widget build(BuildContext context) {
- Widget result = Listener(
- onPointerDown: _handlePointerDown,
- behavior: widget.behavior ?? _defaultBehavior,
- child: widget.child,
- );
- if (!widget.excludeFromSemantics) {
- result = _GestureSemantics(
- behavior: widget.behavior ?? _defaultBehavior,
- assignSemantics: _updateSemanticsForRenderObject,
- child: result,
- );
- }
- return result;
- }
Listener里有个关键的onPointerDown回调,这个方法就是组件处理down事件的入口。界面在收到底层上报触摸事件后,组件会通过_handlePointerDown方法进行处理。
- void _handlePointerDown(PointerDownEvent event) {
- assert(_recognizers != null);
- for (final GestureRecognizer recognizer in _recognizers!.values)
- recognizer.addPointer(event);
- }
这个方法主要作用是将down事件添加到各种手势识别器中。这里我们以TapGestureRecognizer为例查看具体实现逻辑。
- /// * [GestureDetector.onTap], which uses this recognizer.
- /// * [MultiTapGestureRecognizer]
- class TapGestureRecognizer extends BaseTapGestureRecognizer {
- ......
- }
由于TapGestureRecognizer继承BaseTapGestureRecognizer,我们继续向下找
- /// * [TapGestureRecognizer], a ready-to-use tap recognizer that recognizes
- /// taps of the primary button and taps of the secondary button.
- /// * [ModalBarrier], a widget that uses a custom tap recognizer that accepts
- /// any buttons.
- abstract class BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer {
- ......
- }
由于BaseTapGestureRecognizer继承PrimaryPointerGestureRecognizer,我们还的继续向下找
这里由于继承层级比较多,我就不一一列出来了,有兴趣的小伙伴查看源码就可看到。
- /// * [GestureDetector], the widget that is used to detect built-in gestures.
- /// * [RawGestureDetector], the widget that is used to detect custom gestures.
- /// * [debugPrintRecognizerCallbacksTrace], a flag that can be set to help
- /// debug issues with gesture recognizers.
- abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableTreeMixin {
-
-
- /// This method is called for each and all pointers being added. In
- /// most cases, you want to override [addAllowedPointer] instead.
- void addPointer(PointerDownEvent event) {
- _pointerToKind[event.pointer] = event.kind;
- if (isPointerAllowed(event)) {
- addAllowedPointer(event);
- } else {
- handleNonAllowedPointer(event);
- }
- }
- }
我们在抽象类GestureRecognizer找到了addPointer方法。这里面逻辑也简单,就是判断当前触摸事件是否合法,具体的手势识别器会复写该方法,按照识别器需求实现不同的逻辑。
- /// Checks whether or not a pointer is allowed to be tracked by this recognizer.
- @protected
- bool isPointerAllowed(PointerDownEvent event) {
- // Currently, it only checks for device kind. But in the future we could check
- // for other things e.g. mouse button.
- return _supportedDevices == null || _supportedDevices!.contains(event.kind);
- }
我们查看TapGestureRecognizer识别器的方法
- @override
- bool isPointerAllowed(PointerDownEvent event) {
- switch (event.buttons) {
- case kPrimaryButton:
- if (onTapDown == null &&
- onTap == null &&
- onTapUp == null &&
- onTapCancel == null)
- return false;
- break;
- case kSecondaryButton:
- if (onSecondaryTap == null &&
- onSecondaryTapDown == null &&
- onSecondaryTapUp == null &&
- onSecondaryTapCancel == null)
- return false;
- break;
- case kTertiaryButton:
- if (onTertiaryTapDown == null &&
- onTertiaryTapUp == null &&
- onTertiaryTapCancel == null)
- return false;
- break;
- default:
- return false;
- }
- return super.isPointerAllowed(event);
- }
从上面看,对于点击事件来说,只要onTapDown、 onTap、onTapUp和 onTapCancel任一不为空就认为合法(严谨说具体还的看父类)。
我们接着看在手势事件合法时,具体逻辑
- /// Registers a new pointer that's been checked to be allowed by this gesture
- /// recognizer.
- ///
- /// Subclasses of [GestureRecognizer] are supposed to override this method
- /// instead of [addPointer] because [addPointer] will be called for each
- /// pointer being added while [addAllowedPointer] is only called for pointers
- /// that are allowed by this recognizer.
- @protected
- void addAllowedPointer(PointerDownEvent event) { }
这个逻辑分好几层实现,我们一层一层看
第一层BaseTapGestureRecognizer
- @override
- void addAllowedPointer(PointerDownEvent event) {
- assert(event != null);
- if (state == GestureRecognizerState.ready) {
- // If there is no result in the previous gesture arena,
- // we ignore them and prepare to accept a new pointer.
- if (_down != null && _up != null) {
- assert(_down!.pointer == _up!.pointer);
- _reset();
- }
-
- assert(_down == null && _up == null);
- // `_down` must be assigned in this method instead of `handlePrimaryPointer`,
- // because `acceptGesture` might be called before `handlePrimaryPointer`,
- // which relies on `_down` to call `handleTapDown`.
- _down = event;
- }
- if (_down != null) {
- // This happens when this tap gesture has been rejected while the pointer
- // is down (i.e. due to movement), when another allowed pointer is added,
- // in which case all pointers are simply ignored. The `_down` being null
- // means that _reset() has been called, since it is always set at the
- // first allowed down event and will not be cleared except for reset(),
- super.addAllowedPointer(event);
- }
- }
从方法底部super.addAllowedPointer(event);进入到下一层PrimaryPointerGestureRecognizer
第二层PrimaryPointerGestureRecognizer
- @override
- void addAllowedPointer(PointerDownEvent event) {
- super.addAllowedPointer(event);
- 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));
- }
- }
这一层里面添加了一个事件为deadline的定时器,主要用来恢复初始状态,这里不是我们的重点。
继续沿着super.addAllowedPointer(event);进入第三层
第三层OneSequenceGestureRecognizer
- @override
- @protected
- void addAllowedPointer(PointerDownEvent event) {
- startTrackingPointer(event.pointer, event.transform);
- }
这里就是我们手势识别的重点入口。
-
- /// This method also adds this recognizer (or its [team] if it's non-null) to
- /// the gesture arena for the specified pointer.
- ///
- /// This is called by [OneSequenceGestureRecognizer.addAllowedPointer].
- @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);
- }
GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform);
这行将我们手势识别器的handleEvent方法注册到全局单例pointerRouter里,后续事件通过pointerRouter直接回调handleEvent方法,不会继续走上面整个流程。这里也就是说Widget入口RawGestureDetector的Listener只添加了onPointerDown回调的缘故。
_entries[pointer] = _addPointerToArena(pointer);
这行我们将该手势识别器添加到手势识别竞技场中,同其他识别器进行角逐。
具体逻辑如下
- GestureArenaEntry _addPointerToArena(int pointer) {
- if (_team != null)
- return _team!.add(pointer, this);
- return GestureBinding.instance!.gestureArena.add(pointer, this);
- }
这里补充说一下不是所有识别器都是在竞技场角逐出的胜利,例如今天将的点击事件,比较简单直接有竞技场分配胜利,例如后续会讲到的双击事件,则是由自己识别器内部实现具体逻辑。
上面讲了一大堆逻辑,这个流程只是mixin GestureBinding派发事件的一小步,也就是说在命中测试通过的组件在下面循环里面才会被调用,从而才会执行上面RawGestureDetector的Listener中的onPointerDown回调。
- @override // from HitTestDispatcher
- @pragma('vm:notify-debugger-on-exception')
- void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
- ......
- for (final HitTestEntry entry in hitTestResult.path) {
- try {
- entry.target.handleEvent(event.transformed(entry.transform), entry);
- } catch (exception, stack) {
- ......
- },
- ));
- }
- }
- }
这里非常巧妙,在命中测试中,GestureBinding会讲自己也添加到命中列表中,在后续逻辑中有大用处。
每一个组件在上面的循环中,将自己的手势识别器都加入到了竞技场,那么这个竞技场是如何关闭呢,也就是不允许其他选手继续入场。答案就在GestureBinding 的void handleEvent(PointerEvent event, HitTestEntry entry)方法中。
- @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);
- }
- }
这里可以看到pointerRouter,这个就是上面解释过的 GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform);
这里调用pointerRouter.route(event); handleEvent方法会执行。
会执行到PrimaryPointerGestureRecognizer下面方法
- @override
- void handleEvent(PointerEvent event) {
- assert(state != GestureRecognizerState.ready);
- if (state == GestureRecognizerState.possible && event.pointer == primaryPointer) {
- final bool isPreAcceptSlopPastTolerance =
- !_gestureAccepted &&
- preAcceptSlopTolerance != null &&
- _getGlobalDistance(event) > preAcceptSlopTolerance!;
- final bool isPostAcceptSlopPastTolerance =
- _gestureAccepted &&
- postAcceptSlopTolerance != null &&
- _getGlobalDistance(event) > postAcceptSlopTolerance!;
-
- if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
- resolve(GestureDisposition.rejected);
- stopTrackingPointer(primaryPointer!);
- } else {
- handlePrimaryPointer(event);
- }
- }
- stopTrackingIfPointerNoLongerDown(event);
- }
这个方法作用是判断事件为Move事件且移动偏移距离较大,则退出手势竞争,把机会留个其他识别器。
Tap点击事件是在Up事件决胜出来。
- @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);
- }
- }
在Up事件时,竞技场需要清场,需要选择出一个胜利者。
- /// * [hold]
- /// * [release]
- void sweep(int pointer) {
- final _GestureArena? state = _arenas[pointer];
- if (state == null)
- return; // This arena either never existed or has been resolved.
- assert(!state.isOpen);
- if (state.isHeld) {
- state.hasPendingSweep = true;
- assert(_debugLogDiagnostic(pointer, 'Delaying sweep', state));
- return; // This arena is being held for a long-lived member.
- }
- assert(_debugLogDiagnostic(pointer, 'Sweeping', state));
- _arenas.remove(pointer);
- if (state.members.isNotEmpty) {
- // First member wins.
- assert(_debugLogDiagnostic(pointer, 'Winner: ${state.members.first}'));
- state.members.first.acceptGesture(pointer);
- // Give all the other members the bad news.
- for (int i = 1; i < state.members.length; i++)
- state.members[i].rejectGesture(pointer);
- }
- }
很多Flutter 初学者在父Widget和子Widget 都使用GestureDetector 配置点击事件时,只有子组件能收到回调,比较困扰,上面这段就是关键点。
在清场时,判断竞技选手不为空,直接宣判第一个选手胜利,通知其他选手失败。
这里由于采用深度优先的原则,子组件是第一个入场,也就是第一个选手,所以被判定胜利。