• Flutter TapGestureRecognizer 如何工作


    在使用点击事件时,我们使用GestureDetector嵌套子组件的方式。那么点击事件背后的TapGestureRecognizer如何识别出用户在“点击”。

    从GestureDetector源码build方法中可以看到,在我们给GestureDetector组件添加了onTap方法后,GestureDetector会添加TapGestureRecognizer 手势识别器,这个识别器主要功能就是识别点击事件。
    
        final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
    1. @override
    2. Widget build(BuildContext context) {
    3. final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
    4. if (onTapDown != null ||
    5. onTapUp != null ||
    6. onTap != null ||
    7. onTapCancel != null ||
    8. onSecondaryTap != null ||
    9. onSecondaryTapDown != null ||
    10. onSecondaryTapUp != null ||
    11. onSecondaryTapCancel != null||
    12. onTertiaryTapDown != null ||
    13. onTertiaryTapUp != null ||
    14. onTertiaryTapCancel != null
    15. ) {
    16. gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
    17. () => TapGestureRecognizer(debugOwner: this),
    18. (TapGestureRecognizer instance) {
    19. instance
    20. ..onTapDown = onTapDown
    21. ..onTapUp = onTapUp
    22. ..onTap = onTap
    23. ..onTapCancel = onTapCancel
    24. ..onSecondaryTap = onSecondaryTap
    25. ..onSecondaryTapDown = onSecondaryTapDown
    26. ..onSecondaryTapUp = onSecondaryTapUp
    27. ..onSecondaryTapCancel = onSecondaryTapCancel
    28. ..onTertiaryTapDown = onTertiaryTapDown
    29. ..onTertiaryTapUp = onTertiaryTapUp
    30. ..onTertiaryTapCancel = onTertiaryTapCancel;
    31. },
    32. );
    33. }

    在根据设置的不同手势回调,底层添加不同的手势识别器,例如点击,长按,和双击。在最后使用

    RawGestureDetector 组件封装手势识别器Map

    1. return RawGestureDetector(
    2. gestures: gestures,
    3. behavior: behavior,
    4. excludeFromSemantics: excludeFromSemantics,
    5. child: child,
    6. );

    继续查看RawGestureDetector源码,在其build方法中我们找到Listener

    1. @override
    2. Widget build(BuildContext context) {
    3. Widget result = Listener(
    4. onPointerDown: _handlePointerDown,
    5. behavior: widget.behavior ?? _defaultBehavior,
    6. child: widget.child,
    7. );
    8. if (!widget.excludeFromSemantics) {
    9. result = _GestureSemantics(
    10. behavior: widget.behavior ?? _defaultBehavior,
    11. assignSemantics: _updateSemanticsForRenderObject,
    12. child: result,
    13. );
    14. }
    15. return result;
    16. }

    Listener里有个关键的onPointerDown回调,这个方法就是组件处理down事件的入口。界面在收到底层上报触摸事件后,组件会通过_handlePointerDown方法进行处理。

    1. void _handlePointerDown(PointerDownEvent event) {
    2. assert(_recognizers != null);
    3. for (final GestureRecognizer recognizer in _recognizers!.values)
    4. recognizer.addPointer(event);
    5. }

    这个方法主要作用是将down事件添加到各种手势识别器中。这里我们以TapGestureRecognizer为例查看具体实现逻辑。

    1. /// * [GestureDetector.onTap], which uses this recognizer.
    2. /// * [MultiTapGestureRecognizer]
    3. class TapGestureRecognizer extends BaseTapGestureRecognizer {
    4. ......
    5. }

    由于TapGestureRecognizer继承BaseTapGestureRecognizer,我们继续向下找

    1. /// * [TapGestureRecognizer], a ready-to-use tap recognizer that recognizes
    2. /// taps of the primary button and taps of the secondary button.
    3. /// * [ModalBarrier], a widget that uses a custom tap recognizer that accepts
    4. /// any buttons.
    5. abstract class BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer {
    6. ......
    7. }

    由于BaseTapGestureRecognizer继承PrimaryPointerGestureRecognizer,我们还的继续向下找

    这里由于继承层级比较多,我就不一一列出来了,有兴趣的小伙伴查看源码就可看到。

    1. /// * [GestureDetector], the widget that is used to detect built-in gestures.
    2. /// * [RawGestureDetector], the widget that is used to detect custom gestures.
    3. /// * [debugPrintRecognizerCallbacksTrace], a flag that can be set to help
    4. /// debug issues with gesture recognizers.
    5. abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableTreeMixin {
    6. /// This method is called for each and all pointers being added. In
    7. /// most cases, you want to override [addAllowedPointer] instead.
    8. void addPointer(PointerDownEvent event) {
    9. _pointerToKind[event.pointer] = event.kind;
    10. if (isPointerAllowed(event)) {
    11. addAllowedPointer(event);
    12. } else {
    13. handleNonAllowedPointer(event);
    14. }
    15. }
    16. }

    我们在抽象类GestureRecognizer找到了addPointer方法。这里面逻辑也简单,就是判断当前触摸事件是否合法,具体的手势识别器会复写该方法,按照识别器需求实现不同的逻辑。

    1. /// Checks whether or not a pointer is allowed to be tracked by this recognizer.
    2. @protected
    3. bool isPointerAllowed(PointerDownEvent event) {
    4. // Currently, it only checks for device kind. But in the future we could check
    5. // for other things e.g. mouse button.
    6. return _supportedDevices == null || _supportedDevices!.contains(event.kind);
    7. }

    我们查看TapGestureRecognizer识别器的方法

    1. @override
    2. bool isPointerAllowed(PointerDownEvent event) {
    3. switch (event.buttons) {
    4. case kPrimaryButton:
    5. if (onTapDown == null &&
    6. onTap == null &&
    7. onTapUp == null &&
    8. onTapCancel == null)
    9. return false;
    10. break;
    11. case kSecondaryButton:
    12. if (onSecondaryTap == null &&
    13. onSecondaryTapDown == null &&
    14. onSecondaryTapUp == null &&
    15. onSecondaryTapCancel == null)
    16. return false;
    17. break;
    18. case kTertiaryButton:
    19. if (onTertiaryTapDown == null &&
    20. onTertiaryTapUp == null &&
    21. onTertiaryTapCancel == null)
    22. return false;
    23. break;
    24. default:
    25. return false;
    26. }
    27. return super.isPointerAllowed(event);
    28. }

    从上面看,对于点击事件来说,只要onTapDown、 onTap、onTapUp和 onTapCancel任一不为空就认为合法(严谨说具体还的看父类)。

    我们接着看在手势事件合法时,具体逻辑

    1. /// Registers a new pointer that's been checked to be allowed by this gesture
    2. /// recognizer.
    3. ///
    4. /// Subclasses of [GestureRecognizer] are supposed to override this method
    5. /// instead of [addPointer] because [addPointer] will be called for each
    6. /// pointer being added while [addAllowedPointer] is only called for pointers
    7. /// that are allowed by this recognizer.
    8. @protected
    9. void addAllowedPointer(PointerDownEvent event) { }

    这个逻辑分好几层实现,我们一层一层看

    第一层BaseTapGestureRecognizer

    1. @override
    2. void addAllowedPointer(PointerDownEvent event) {
    3. assert(event != null);
    4. if (state == GestureRecognizerState.ready) {
    5. // If there is no result in the previous gesture arena,
    6. // we ignore them and prepare to accept a new pointer.
    7. if (_down != null && _up != null) {
    8. assert(_down!.pointer == _up!.pointer);
    9. _reset();
    10. }
    11. assert(_down == null && _up == null);
    12. // `_down` must be assigned in this method instead of `handlePrimaryPointer`,
    13. // because `acceptGesture` might be called before `handlePrimaryPointer`,
    14. // which relies on `_down` to call `handleTapDown`.
    15. _down = event;
    16. }
    17. if (_down != null) {
    18. // This happens when this tap gesture has been rejected while the pointer
    19. // is down (i.e. due to movement), when another allowed pointer is added,
    20. // in which case all pointers are simply ignored. The `_down` being null
    21. // means that _reset() has been called, since it is always set at the
    22. // first allowed down event and will not be cleared except for reset(),
    23. super.addAllowedPointer(event);
    24. }
    25. }

    从方法底部super.addAllowedPointer(event);进入到下一层PrimaryPointerGestureRecognizer

    第二层PrimaryPointerGestureRecognizer

    1. @override
    2. void addAllowedPointer(PointerDownEvent event) {
    3. super.addAllowedPointer(event);
    4. if (state == GestureRecognizerState.ready) {
    5. _state = GestureRecognizerState.possible;
    6. _primaryPointer = event.pointer;
    7. _initialPosition = OffsetPair(local: event.localPosition, global: event.position);
    8. if (deadline != null)
    9. _timer = Timer(deadline!, () => didExceedDeadlineWithEvent(event));
    10. }
    11. }

    这一层里面添加了一个事件为deadline的定时器,主要用来恢复初始状态,这里不是我们的重点。

    继续沿着super.addAllowedPointer(event);进入第三层

    第三层OneSequenceGestureRecognizer

    1. @override
    2. @protected
    3. void addAllowedPointer(PointerDownEvent event) {
    4. startTrackingPointer(event.pointer, event.transform);
    5. }

    这里就是我们手势识别的重点入口。

    1. /// This method also adds this recognizer (or its [team] if it's non-null) to
    2. /// the gesture arena for the specified pointer.
    3. ///
    4. /// This is called by [OneSequenceGestureRecognizer.addAllowedPointer].
    5. @protected
    6. void startTrackingPointer(int pointer, [Matrix4? transform]) {
    7. GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform);
    8. _trackedPointers.add(pointer);
    9. assert(!_entries.containsValue(pointer));
    10. _entries[pointer] = _addPointerToArena(pointer);
    11. }

     GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform);

    这行将我们手势识别器的handleEvent方法注册到全局单例pointerRouter里,后续事件通过pointerRouter直接回调handleEvent方法,不会继续走上面整个流程。这里也就是说Widget入口RawGestureDetector的Listener只添加了onPointerDown回调的缘故。

    _entries[pointer] = _addPointerToArena(pointer);

    这行我们将该手势识别器添加到手势识别竞技场中,同其他识别器进行角逐。

    具体逻辑如下

    1. GestureArenaEntry _addPointerToArena(int pointer) {
    2. if (_team != null)
    3. return _team!.add(pointer, this);
    4. return GestureBinding.instance!.gestureArena.add(pointer, this);
    5. }

    这里补充说一下不是所有识别器都是在竞技场角逐出的胜利,例如今天将的点击事件,比较简单直接有竞技场分配胜利,例如后续会讲到的双击事件,则是由自己识别器内部实现具体逻辑。

    上面讲了一大堆逻辑,这个流程只是mixin GestureBinding派发事件的一小步,也就是说在命中测试通过的组件在下面循环里面才会被调用,从而才会执行上面RawGestureDetector的Listener中的onPointerDown回调。

    1. @override // from HitTestDispatcher
    2. @pragma('vm:notify-debugger-on-exception')
    3. void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
    4. ......
    5. for (final HitTestEntry entry in hitTestResult.path) {
    6. try {
    7. entry.target.handleEvent(event.transformed(entry.transform), entry);
    8. } catch (exception, stack) {
    9. ......
    10. },
    11. ));
    12. }
    13. }
    14. }

    这里非常巧妙,在命中测试中,GestureBinding会讲自己也添加到命中列表中,在后续逻辑中有大用处。

    每一个组件在上面的循环中,将自己的手势识别器都加入到了竞技场,那么这个竞技场是如何关闭呢,也就是不允许其他选手继续入场。答案就在GestureBinding 的void handleEvent(PointerEvent event, HitTestEntry entry)方法中。

    1. @override // from HitTestTarget
    2. void handleEvent(PointerEvent event, HitTestEntry entry) {
    3. pointerRouter.route(event);
    4. if (event is PointerDownEvent) {
    5. gestureArena.close(event.pointer);
    6. } else if (event is PointerUpEvent) {
    7. gestureArena.sweep(event.pointer);
    8. } else if (event is PointerSignalEvent) {
    9. pointerSignalResolver.resolve(event);
    10. }
    11. }

    这里可以看到pointerRouter,这个就是上面解释过的 GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform);

    这里调用pointerRouter.route(event); handleEvent方法会执行。

    会执行到PrimaryPointerGestureRecognizer下面方法

    1. @override
    2. void handleEvent(PointerEvent event) {
    3. assert(state != GestureRecognizerState.ready);
    4. if (state == GestureRecognizerState.possible && event.pointer == primaryPointer) {
    5. final bool isPreAcceptSlopPastTolerance =
    6. !_gestureAccepted &&
    7. preAcceptSlopTolerance != null &&
    8. _getGlobalDistance(event) > preAcceptSlopTolerance!;
    9. final bool isPostAcceptSlopPastTolerance =
    10. _gestureAccepted &&
    11. postAcceptSlopTolerance != null &&
    12. _getGlobalDistance(event) > postAcceptSlopTolerance!;
    13. if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
    14. resolve(GestureDisposition.rejected);
    15. stopTrackingPointer(primaryPointer!);
    16. } else {
    17. handlePrimaryPointer(event);
    18. }
    19. }
    20. stopTrackingIfPointerNoLongerDown(event);
    21. }

    这个方法作用是判断事件为Move事件且移动偏移距离较大,则退出手势竞争,把机会留个其他识别器。

    Tap点击事件是在Up事件决胜出来。

    1. @override // from HitTestTarget
    2. void handleEvent(PointerEvent event, HitTestEntry entry) {
    3. pointerRouter.route(event);
    4. if (event is PointerDownEvent) {
    5. gestureArena.close(event.pointer);
    6. } else if (event is PointerUpEvent) {
    7. gestureArena.sweep(event.pointer);
    8. } else if (event is PointerSignalEvent) {
    9. pointerSignalResolver.resolve(event);
    10. }
    11. }

    在Up事件时,竞技场需要清场,需要选择出一个胜利者。

    1. /// * [hold]
    2. /// * [release]
    3. void sweep(int pointer) {
    4. final _GestureArena? state = _arenas[pointer];
    5. if (state == null)
    6. return; // This arena either never existed or has been resolved.
    7. assert(!state.isOpen);
    8. if (state.isHeld) {
    9. state.hasPendingSweep = true;
    10. assert(_debugLogDiagnostic(pointer, 'Delaying sweep', state));
    11. return; // This arena is being held for a long-lived member.
    12. }
    13. assert(_debugLogDiagnostic(pointer, 'Sweeping', state));
    14. _arenas.remove(pointer);
    15. if (state.members.isNotEmpty) {
    16. // First member wins.
    17. assert(_debugLogDiagnostic(pointer, 'Winner: ${state.members.first}'));
    18. state.members.first.acceptGesture(pointer);
    19. // Give all the other members the bad news.
    20. for (int i = 1; i < state.members.length; i++)
    21. state.members[i].rejectGesture(pointer);
    22. }
    23. }

    很多Flutter 初学者在父Widget和子Widget 都使用GestureDetector 配置点击事件时,只有子组件能收到回调,比较困扰,上面这段就是关键点。

    在清场时,判断竞技选手不为空,直接宣判第一个选手胜利,通知其他选手失败。

    这里由于采用深度优先的原则,子组件是第一个入场,也就是第一个选手,所以被判定胜利。

  • 相关阅读:
    Redis数据结构:HyperLogLog
    【SpringMVC】JSON数据返回及异常处理(相信我看完就懂的差不多了)
    英语语音技巧
    代理IP技术详解:原理、类型与实际应用案例分析
    深入理解ngx_http_upstream_vnswrr_module负载均衡模块
    香农-范诺编码(Shannon–Fano Coding)
    React笔记(五)hook
    540. 有序数组中的单一元素
    flex布局与float布局
    计算机图形学中的曲线问题
  • 原文地址:https://blog.csdn.net/bawomingtian123/article/details/126133874