一文搞懂Flutter的手势事件——事件分发与冲突处理详解

 633f48ce3b8128f456cd93e045a25d31.gif

本文字数:43617

预计阅读时间:110分钟

前言

之前有两篇文章都围绕着runApp()进行展开,讲解了布局绘制的详细过程。

  • https://www.jianshu.com/p/2ef749ff4d40/

  • https://www.jianshu.com/p/f37f8da235ec

那么接下来我们想详细的说一说Flutter是如何处理手势事件的。本文将通过源码详细分析Flutter的事件分发与冲突处理过程,并通过示例说明不同冲突的处理方式。本文的组织架构如下:

  • 手势事件的初始化

  • 命中测试

    • PointerEvent的封装

    • hitTest()

  • dispatchEvent()

  • GestureDetector

    • onTap

    • onLongPress

    • onDoubleTap

    • onVerticalDragDown

  • 手势事件拦截

  • 总结

手势事件的初始化

还是先回到我们熟悉的runApp()

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

前面的文章介绍过WidgetsFlutterBinding混合了很多mixin

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding

手势相关的是GestureBinding,我们来看看它的initInstances()初始化的实现:

@override
  void initInstances() {
    super.initInstances();
    _instance = this;
    platformDispatcher.onPointerDataPacket = _handlePointerDataPacket;
  }

platformDispatcher注册了onPointerDataPacket回调,其实现是_handlePointerDataPacket()。我们先追踪一下onPointerDataPacket的回调时机:

PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket;
  PointerDataPacketCallback? _onPointerDataPacket;
  Zone _onPointerDataPacketZone = Zone.root;
  set onPointerDataPacket(PointerDataPacketCallback? callback) {
    _onPointerDataPacket = callback;
    _onPointerDataPacketZone = Zone.current;
  }

继续追踪onPointerDataPacket的调用时机:

// Called from the engine, via hooks.dart
  void _dispatchPointerDataPacket(ByteData packet) {
    if (onPointerDataPacket != null) {
      _invoke1<PointerDataPacket>(
        onPointerDataPacket,
        _onPointerDataPacketZone,
        _unpackPointerDataPacket(packet),
      );
    }
  }

void _invoke1<A>(void Function(A a)? callback, Zone zone, A arg) {
  if (callback == null) {
    return;
  }

  assert(zone != null);

  if (identical(zone, Zone.current)) {
    callback(arg);
  } else {
    zone.runUnaryGuarded<A>(callback, arg);
  }
}

@pragma('vm:entry-point')
void _dispatchPointerDataPacket(ByteData packet) {
  PlatformDispatcher.instance._dispatchPointerDataPacket(packet);
}

从注释上我们就可得知,_dispatchPointerDataPacket()就是接收从framework层传递的手势事件,并进行处理的时机。_invoke1()其实就是调用onPointerDataPacket并将_unpackPointerDataPacket()的返回值回调。其中packet是一组未经处理的的ByteData,通过_unpackPointerDataPacket()方法对其进行解包处理,生成手势事件所需的实体类PointerData

static PointerDataPacket _unpackPointerDataPacket(ByteData packet) {
    const int kStride = Int64List.bytesPerElement;
    const int kBytesPerPointerData = _kPointerDataFieldCount * kStride;
    final int length = packet.lengthInBytes ~/ kBytesPerPointerData;
    assert(length * kBytesPerPointerData == packet.lengthInBytes);
    final List<PointerData> data = <PointerData>[];
    for (int i = 0; i < length; ++i) {
      int offset = i * _kPointerDataFieldCount;
      data.add(PointerData(
        embedderId: packet.getInt64(kStride * offset++, _kFakeHostEndian),
        timeStamp: Duration(microseconds: packet.getInt64(kStride * offset++, _kFakeHostEndian)),
//...
        scale: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
        rotation: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
      ));
      assert(offset == (i + 1) * _kPointerDataFieldCount);
    }
    return PointerDataPacket(data: data);
  }

好了到此为止我们可以得知,在启动app后,由GestureBinding注册手势的回调事件,当engine层发送来手势事件后,由PlatformDispatcher封装成PointerData实体,最终回调至GestureBinding进行处理,接下来我们看接收到手势事件后的处理流程。

命中测试

PointerEvent的封装 

在真正处理手势之前,第一步就是将手势事件封装成业务可用的PointerEvent,我们回到GestureBindinginitInstances()

@override
  void initInstances() {
    super.initInstances();
    _instance = this;
    platformDispatcher.onPointerDataPacket = _handlePointerDataPacket;
  }

跟踪_handlePointerDataPacket的实现:

void _handlePointerDataPacket(ui.PointerDataPacket packet) {
    // We convert pointer data to logical pixels so that e.g. the touch slop can be
    // defined in a device-independent manner.
    _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
    if (!locked) {
      _flushPointerEventQueue();
    }
  }

首先就是把解包后的packet.data通过PointerEventConverter.expand()再做次转换:

static Iterable<PointerEvent> expand(Iterable<ui.PointerData> data, double devicePixelRatio) {
    return data
        .where((ui.PointerData datum) => datum.signalKind != ui.PointerSignalKind.unknown)
        .map((ui.PointerData datum) {
//...
          switch (datum.signalKind ?? ui.PointerSignalKind.none) {
            case ui.PointerSignalKind.none:
              switch (datum.change) {
         //...
                case ui.PointerChange.down:
                  return PointerDownEvent(
                    timeStamp: timeStamp,
                    pointer: datum.pointerIdentifier,
                    kind: kind,
                    device: datum.device,
                    position: position,
                    buttons: _synthesiseDownButtons(datum.buttons, kind),
                    obscured: datum.obscured,
                    pressure: datum.pressure,
//...
                  );
                case ui.PointerChange.move:
                  return PointerMoveEvent(
                    timeStamp: timeStamp,
                    pointer: datum.pointerIdentifier,
                    kind: kind,
                    device: datum.device,
                    position: position,
                    delta: delta,
                    buttons: _synthesiseDownButtons(datum.buttons, kind),
                    obscured: datum.obscured,
//...
                  );
//...
            case ui.PointerSignalKind.unknown:
            default: // ignore: no_default_cases, to allow adding a new [PointerSignalKind]
                     // TODO(moffatman): Remove after landing https://github.com/flutter/engine/pull/34402
              // This branch should already have 'unknown' filtered out, but
              // we don't want to return anything or miss if someone adds a new
              // enumeration to PointerSignalKind.
              throw StateError('Unreachable');
          }
        });
  }

截取了部分代码,大致就是将packet.data转换成PointerEvent,并添加到_pendingPointerEvents列表中。接下来我们看_flushPointerEventQueue()的实现:

void _flushPointerEventQueue() {
    assert(!locked);

    while (_pendingPointerEvents.isNotEmpty) {
      handlePointerEvent(_pendingPointerEvents.removeFirst());
    }
  }
void handlePointerEvent(PointerEvent event) {
    assert(!locked);

    if (resamplingEnabled) {
      _resampler.addOrDispatch(event);
      _resampler.sample(samplingOffset, _samplingClock);
      return;
    }

    // Stop resampler if resampling is not enabled. This is a no-op if
    // resampling was never enabled.
    _resampler.stop();
    _handlePointerEventImmediately(event);
  }
void _handlePointerEventImmediately(PointerEvent event) {
    HitTestResult? hitTestResult;
    if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent || event is PointerPanZoomStartEvent) {
      assert(!_hitTests.containsKey(event.pointer));
      hitTestResult = HitTestResult();
      hitTest(hitTestResult, event.position);
      if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
        _hitTests[event.pointer] = hitTestResult;
      }
//...
    } else if (event is PointerUpEvent || event is PointerCancelEvent || event is PointerPanZoomEndEvent) {
      hitTestResult = _hitTests.remove(event.pointer);
    } else if (event.down || event is PointerPanZoomUpdateEvent) {
      hitTestResult = _hitTests[event.pointer];
    }
//...
    if (hitTestResult != null ||
        event is PointerAddedEvent ||
        event is PointerRemovedEvent) {
      assert(event.position != null);
      dispatchEvent(event, hitTestResult);
    }
  }

这段代码的整体思路是:

  • 如果eventPointerDownEvent等四种event之一时(我们假设创建的是一个移动端app,这四种event只考虑PointerDownEvent的情况),创建一个HitTestResult对象,并调用hitTest()方法,然后将hitTestResult赋值给_hitTests[event.pointer];

  • 如果是PointerUpEventPointerCancelEvent,那么将此hitTestResult_hitTests中移除并返回给hitTestResult对象;

  • 最后执行dispatchEvent(event, hitTestResult);

说完PointerEvent的封装后,Flutter是如何处理这些手势事件的呢?如何确定哪些Widget响应手势事件,并确认它们的优先级呢?接下来我们讲一下hitTest()的实现,即命中测试。

hitTest()

hitTest()需要确定出都哪些widget对应的RenderObject可能会响应手势事件,并给他们设置命中响应的优先级。这个步骤是非常重要的,可以确定最终响应手势事件的Widget。在此我们先说个结论,子优先于父响应手势事件。我们先来看看HitTestResult的结构:

HitTestResult()
     : _path = <HitTestEntry>[],
       _transforms = <Matrix4>[Matrix4.identity()],
       _localTransforms = <_TransformPart>[];
HitTestEntry(this.target);

其中_path是个HitTestEntry数组,HitTestResult每执行一次add()方法就会添加一个HitTestEntry对象到_path中,HitTestResulttarget对象通常就是一个RenderObject对象,也就是说_path是用来记录手势命中的RenderObject对象数组。_transforms是记录目标RenderObject对象相对于Global坐标系的位置。_localTransforms是记录目标RenderObject对象相对于Parent的位置。我们继续看hitTest(hitTestResult, event.position);的实现:

@override // from HitTestable
  void hitTest(HitTestResult result, Offset position) {
    result.add(HitTestEntry(this));
  }

创建一个HitTestEntry对象并添加到HitTestResult中。由于mixinRendererBindingGesturesBinding的子类,而RendererBinding实现了hitTest()方法,所以我们看看RendererBindinghitTest()的实现:

@override
  void hitTest(HitTestResult result, Offset position) {
    assert(renderView != null);
    assert(result != null);
    assert(position != null);
    renderView.hitTest(result, position: position);
    super.hitTest(result, position);
  }

在调用super.hitTest()之前,先调用了renderViewhitTest()方法,renderView我们很了解了,就是App的根RenderObject

bool hitTest(HitTestResult result, { required Offset position }) {
    if (child != null) {
      child!.hitTest(BoxHitTestResult.wrap(result), position: position);
    }
    result.add(HitTestEntry(this));
    return true;
  }

首先判断是否有child,如果是的话需要先执行childhitTest()方法,再将自己封装成一个HitTestEntry添加到HitTestResult中。我们来看看child!.hitTest()方法的实现:

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;
  }

如果手势的position在当前RenderObject_size范围里,判断hitTestChildren()hitTestSelf()是不是返回true,如果是的话将自己封装成BoxHitTestEntry添加到HitTestResult中。也就是说只要子或自己命中手势事件,就添加到HitTestResult。在这里要注意的是,会优先判断hitTestChildren(),这个方法是判断子是否有命中手势,如果子命中了就不会再走hitTestSelf()的判断,从而子的优先级较高,会被优先加入到HitTestResult_path队列中。我们看看hitTestChildren()的实现:

@protected
  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) => false;

hitTestChildren()是个抽象方法,由子类实现。我们举个例子,假设当前Widget是个Align,它所对应的RenderObject对象其实是个RenderShiftedBox,它的hitTestChildren()实现如下:

@override
  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
    if (child != null) {
      final BoxParentData childParentData = child!.parentData! as BoxParentData;
      return result.addWithPaintOffset(
        offset: childParentData.offset,
        position: position,
        hitTest: (BoxHitTestResult result, Offset transformed) {
          assert(transformed == position - childParentData.offset);
          return child!.hitTest(result, position: transformed);
        },
      );
    }
    return false;
  }
bool addWithPaintOffset({
    required Offset? offset,
    required Offset position,
    required BoxHitTest hitTest,
  }) {
    assert(position != null);
    assert(hitTest != null);
    final Offset transformedPosition = offset == null ? position : position - offset;
    if (offset != null) {
      pushOffset(-offset);
    }
    final bool isHit = hitTest(this, transformedPosition);
    if (offset != null) {
      popTransform();
    }
    return isHit;
  }

其中positionPointerEventpositionchildParentData记录了子相对于父的偏移量。执行result.addWithPaintOffset()方法,其中hitTest是个callback,执行的是child!.hitTest(result, position: transformed)。也就是说hitTestChildren()方法是遍历整个RenderObject树,递归执行 child!.hitTest()方法,去判断子是否有命中手势事件。如果已经没有子或者子没有命中的话,才会判断自己是否命中,我们回过头来看看hitTestSelf()的实现:

@protected
  bool hitTestSelf(Offset position) => false;

它也是个抽象方法,假设我们点击的是个Image,它所对应的RenderObjectRenderImage,其hitTestSelf()的实现如下:

@override
  bool hitTestSelf(Offset position) => true;

只要手势事件的positionRenderImage_size范围内,就命中手势事件。到此为止,hitTest的过程我们就梳理完成了。总结一下,hitTest实际上就是判断当前Widget对应的RenderObject是否命中手势事件。

  • 在移动端只有PointerDownEvent事件会进行命中测试;

  • 遍历整个RenderObject树,优先对子进行命中测试,若子命中即先添加进HitTestResult中;

  • 若子没有命中,则判断自己是否命中;

  • 只要子或者自己命中,都会加到HitTestResult中。

我们将hitTest()的处理流程总结成流程图如下:

5c42a4808a981fc52ed636fe839effb0.jpeg

在命中测试完成得到HitTestResult后,Flutter就可以进行事件分发了,分发给HitTestResult的每一个成员进行处理,具体的处理流程是怎样的呢?怎么处理冲突的呢?我们继续分析dispatchEvent()的实现。

dispatchEvent()

dispatchEvent()对手势事件做分发,我们回到之前的代码_handlePointerEventImmediately()

void _handlePointerEventImmediately(PointerEvent event) {
    HitTestResult? hitTestResult;
    if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent || event is PointerPanZoomStartEvent) {
      assert(!_hitTests.containsKey(event.pointer));
      hitTestResult = HitTestResult();
      hitTest(hitTestResult, event.position);
      if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
        _hitTests[event.pointer] = hitTestResult;
      }
//...
    } else if (event is PointerUpEvent || event is PointerCancelEvent || event is PointerPanZoomEndEvent) {
      hitTestResult = _hitTests.remove(event.pointer);
    } else if (event.down || event is PointerPanZoomUpdateEvent) {
      hitTestResult = _hitTests[event.pointer];
    }
//...
    if (hitTestResult != null ||
        event is PointerAddedEvent ||
        event is PointerRemovedEvent) {
      assert(event.position != null);
      dispatchEvent(event, hitTestResult);
    }
  }

hitTestResult不为空,即有命中的情况下,则执行dispatchEvent()方法,其实现如下:

void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
    assert(!locked);
    if (hitTestResult == null) {
      assert(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);
      } catch (exception, stack) {
//...
      }
    }
  }

遍历HitTestResult.path,执行entry.target.handleEvent()方法,target就是每个命中的RenderObject对象。到这里我们可以清晰的看到,先进入HitTestResult.path队列里的target优先执行handleEvent(),上一章节我们提到子优先于父被加入HitTestResult.path,所以子也优先于父实现handleEvent()

/// Override this method to receive events.
  void handleEvent(PointerEvent event, HitTestEntry<HitTestTarget> entry);

handleEvent()HitTestTarget这个抽象类的方法,需要其子类去实现,这里要说的是RenderObject实现了HitTestTarget,也就是说是由RenderObject去实现handleEvent()。另外GestureBinding也是个HitTestTarget,我们看看GestureBinding的实现:

@override // from HitTestTarget
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    pointerRouter.route(event);
    if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
      gestureArena.close(event.pointer);
    } else if (event is PointerUpEvent || event is PointerPanZoomEndEvent) {
      gestureArena.sweep(event.pointer);
    } else if (event is PointerSignalEvent) {
      pointerSignalResolver.resolve(event);
    }
  }

首先会执行 pointerRouter.route(event),这个方法的作用是将手势事件首先分发给各个PointerRoute去处理:

void route(PointerEvent event) {
    final Map<PointerRoute, Matrix4?>? routes = _routeMap[event.pointer];
    final Map<PointerRoute, Matrix4?> copiedGlobalRoutes = Map<PointerRoute, Matrix4?>.of(_globalRoutes);
    if (routes != null) {
      _dispatchEventToRoutes(
        event,
        routes,
        Map<PointerRoute, Matrix4?>.of(routes),
      );
    }
    _dispatchEventToRoutes(event, _globalRoutes, copiedGlobalRoutes);
  }

_routeMap是什么呢?我们举个例子:

@override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MaterialApp(
        home: Scaffold(
            body: Center(
              child: GestureDetector(
                onTap: () {
                  print("onTap");
                },
                onLongPress: () {
                  print("onLongPress");
                },
                child: const Text("GestureDetector test"),
              ),
            ),

      ),
    );
  }

使用GestureDetector监听onTaponLongPress事件。代码运行后我们都知道结果:单击Text会打印onTaplog,长按Text会打印onLongPresslog。具体的原因之后我们再详细分析,先说个结论:onTaponLongPress将会注册两个PointerRoute存到_routeMap中。PointerRoute实际上是个callback。我们回到route()方法,看_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);
      }
    });
  }

遍历copiedRoutes执行_dispatch()方法:

@pragma('vm:notify-debugger-on-exception')
  void _dispatch(PointerEvent event, PointerRoute route, Matrix4? transform) {
    try {
      event = event.transformed(transform);
      route(event);
    } catch (exception, stack) {
//...
    }
  }

执行route(event)。具体的实现之后的章节再做说明。我们回到handleEvent()方法:

@override // from HitTestTarget
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    pointerRouter.route(event);
    if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
      gestureArena.close(event.pointer);
    } else if (event is PointerUpEvent || event is PointerPanZoomEndEvent) {
      gestureArena.sweep(event.pointer);
    } else if (event is PointerSignalEvent) {
      pointerSignalResolver.resolve(event);
    }
  }

如果是PointerDownEvent,则执行gestureArena.close(event.pointer),如果是PointerUpEvent,则执行gestureArena.sweep(event.pointer)。这时候我们就有个疑问,gestureArena是什么?gestureArena是个GestureArenaManager对象,是个手势竞技场管理类,它有个成员变量_arenas

final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};

_GestureArena记录了所有手势竞技成员members

final List<GestureArenaMember> members = <GestureArenaMember>[];

之前例子中我们提到的单击和长按其实各自都会被封装成一个GestureArenaMember,而GestureArenaManager的作用就是判定竞技场中GestureArenaMember的胜负,即最终响应的是什么事件。我们再回到handleEvent()方法分析gestureArena.close(event.pointer)的实现:

void close(int pointer) {
    final _GestureArena? state = _arenas[pointer];
    if (state == null) {
      return; // This arena either never existed or has been resolved.
    }
    state.isOpen = false;
    assert(_debugLogDiagnostic(pointer, 'Closing', state));
    _tryToResolveArena(pointer, state);
  }

PointerDownEvent的时候,会分发手势事件给所有的PointerRoute,待PointerRoute都注册完成后,关闭手势竞技场,调用_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!);
    }
  }

这个方法的作用是确定竞技场里的哪个GestureArenaMember胜利。比方说我们之前的例子如果只注册了onTap事件而没有注册onLongPress,那么state.members.length == 1true,则调用_resolveByDefault(pointer, state)

void _resolveByDefault(int pointer, _GestureArena state) {
    if (!_arenas.containsKey(pointer)) {
      return; // This arena has already resolved.
    }
    assert(_arenas[pointer] == state);
    assert(!state.isOpen);
    final List<GestureArenaMember> members = state.members;
    assert(members.length == 1);
    _arenas.remove(pointer);
    assert(_debugLogDiagnostic(pointer, 'Default winner: ${state.members.first}'));
    state.members.first.acceptGesture(pointer);
  }

_arenas里移除当前pointer,竞技场中唯一的成员调用acceptGesture(pointer)宣布胜利。acceptGesture(pointer)是个抽象方法,之后我们再举例说明它的实现。如果注册了多个PointerRoutestate.eagerWinner != null说明竞技场里有的member优先级高,会直接宣告胜利。我们看一下_resolveInFavorOf(pointer, state, state.eagerWinner!)的实现:

void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {
    assert(state == _arenas[pointer]);
    assert(state != null);
    assert(state.eagerWinner == null || state.eagerWinner == member);
    assert(!state.isOpen);
    _arenas.remove(pointer);
    for (final GestureArenaMember rejectedMember in state.members) {
      if (rejectedMember != member) {
        rejectedMember.rejectGesture(pointer);
      }
    }
    member.acceptGesture(pointer);
  }

宣告其它member失败,且宣告eagerWinner胜利。分析完gestureArena.close(event.pointer),我们再回到handleEvent(),在PointerUpEvent时会执行gestureArena.sweep(event.pointer)

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);
      }
    }
  }

它的作用是清扫手势竞技场。如果state.isHeldtrue,说明竞技场里有的成员生命周期比较长,需要等待,所以先不做处理。比方说上面的例子中onDoubleTap事件就会设置state.isHeldtrue。否则如将会宣告竞技场中第一个加入的成员胜利,其他成员失败。到此为止,GestureBinding中的dispatchEvent()就分析完毕了,我们总结一下:

  • 先遍历HitTestEntry,执行entry.target.handleEvent()方法,顺序是子优先;

  • 针对每一个target,执行PointerRoute.route(event),这个方法的作用是将手势事件首先分发给PointerRouter_routeMap的各个成员去处理;

  • PointerDownEvent的时候关闭手势竞技场,并根据条件判定当前就可决定的胜利成员;

  • PointerUpEvent时清扫手势竞技场,并最终判定胜利的成员。

我们将dispatchEvent()的处理流程总结成流程图如下:

78b03c579ddd99ac2f57b99124abc6a6.jpeg

到此为止,我们知道了手势事件是怎么分发和解决冲突的。接下来我们通过分析GestureDetector来看一下细节的实现。

GestureDetector

我们举个例子:

@override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'BuildContext Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MaterialApp(
        home: Scaffold(
            body: Center(
              child: GestureDetector(
                onTap: () {
                  print("onTap");
                },
                onLongPress: () {
                  print("onLongPress");
                },
                onDoubleTap: () {
                  print("onDoubleTap");
                },
                onVerticalDragDown: (details) {
                  print("onVerticalDragDown");
                },
                child: Container(
                  alignment: Alignment.center,
                  height: 500,
                  color: Colors.amber,
                  child: const Text("GestureDetector test"),
                ),
              ),
            ),
      ),
    );
  }

以上代码的执行结果我们非常清楚:

  • 单击打印onTap

  • 长按打印onLongPress

  • 双击打印onDoubleTap

  • 竖向拖拽打印onVerticalDragDown

以上我们可已知onTap会分别和其他三个事件冲突。假设我们先只监听onTap事件,看看GestureDetector的核心源码实现:

onTap

@override
  Widget build(BuildContext context) {
    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
    final DeviceGestureSettings? gestureSettings = MediaQuery.maybeOf(context)?.gestureSettings;

    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
            ..gestureSettings = gestureSettings;
        },
      );
    }

//...

    return RawGestureDetector(
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
      child: child,
    );
  }

创建一个TapGestureRecognizer对象,并存到gestures这个Map中,TapGestureRecognizer的继承关系如下:

class TapGestureRecognizer extends BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer extends OneSequenceGestureRecognizer extends GestureRecognizer extends GestureArenaMember

我们可以看到其实它就是一个GestureArenaMember竞技场成员对象。然后返回一个RawGestureDetectorWidgetRawGestureDetector是个StatefulWidget,我们看看它对应的RawGestureDetectorStateinitState()的实现:

@override
  void initState() {
    super.initState();
    _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
    _syncAll(widget.gestures);
  }
void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
    assert(_recognizers != null);
    final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!;
    _recognizers = <Type, GestureRecognizer>{};
    for (final Type type in gestures.keys) {
      assert(gestures[type] != null);
      assert(gestures[type]!._debugAssertTypeMatches(type));
      assert(!_recognizers!.containsKey(type));
      _recognizers![type] = oldRecognizers[type] ?? gestures[type]!.constructor();
      assert(_recognizers![type].runtimeType == type, 'GestureRecognizerFactory of type $type created a GestureRecognizer of type ${_recognizers![type].runtimeType}. The GestureRecognizerFactory must be specialized with the type of the class that it returns from its constructor method.');
      gestures[type]!.initializer(_recognizers![type]!);
    }
    for (final Type type in oldRecognizers.keys) {
      if (!_recognizers!.containsKey(type)) {
        oldRecognizers[type]!.dispose();
      }
    }
  }

_syncAll()方法的作用是将传入的gestures_recognizers对象赋值。再看看build()的实现:

@override
  Widget build(BuildContext context) {
    Widget result = Listener(
      onPointerDown: _handlePointerDown,
      onPointerPanZoomStart: _handlePointerPanZoomStart,
      behavior: widget.behavior ?? _defaultBehavior,
      child: widget.child,
    );
    if (!widget.excludeFromSemantics) {
      result = _GestureSemantics(
        behavior: widget.behavior ?? _defaultBehavior,
        assignSemantics: _updateSemanticsForRenderObject,
        child: result,
      );
    }
    return result;
  }

返回一个Listener。这个Listener我们在实践中也非常熟悉。监听并识别所有最底层的基础手势事件,但不做处理。它是个SingleChildRenderObjectWidget,我们看看其createRenderObject的实现:

@override
  RenderPointerListener createRenderObject(BuildContext context) {
    return RenderPointerListener(
      onPointerDown: onPointerDown,
      onPointerMove: onPointerMove,
      onPointerUp: onPointerUp,
      onPointerHover: onPointerHover,
      onPointerCancel: onPointerCancel,
      onPointerPanZoomStart: onPointerPanZoomStart,
      onPointerPanZoomUpdate: onPointerPanZoomUpdate,
      onPointerPanZoomEnd: onPointerPanZoomEnd,
      onPointerSignal: onPointerSignal,
      behavior: behavior,
    );
  }

在之前的章节我们提到过,在dispatchEvent()时会遍历HitTestEntry,执行entry.target.handleEvent()方法,target就是每个命中的RenderObject对象。所以我们看看RenderPointerListenerhandleEvent()的实现:

@override
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    assert(debugHandleEvent(event, entry));
    if (event is PointerDownEvent) {
      return onPointerDown?.call(event);
    }
    if (event is PointerMoveEvent) {
      return onPointerMove?.call(event);
    }
    if (event is PointerUpEvent) {
      return onPointerUp?.call(event);
    }
    if (event is PointerHoverEvent) {
      return onPointerHover?.call(event);
    }
    if (event is PointerCancelEvent) {
      return onPointerCancel?.call(event);
    }
    if (event is PointerPanZoomStartEvent) {
      return onPointerPanZoomStart?.call(event);
    }
    if (event is PointerPanZoomUpdateEvent) {
      return onPointerPanZoomUpdate?.call(event);
    }
    if (event is PointerPanZoomEndEvent) {
      return onPointerPanZoomEnd?.call(event);
    }
    if (event is PointerSignalEvent) {
      return onPointerSignal?.call(event);
    }
  }

它非常的简单,就是将基础的手势事件回调回去。我们回到RawGestureDetectorStatebuild()

@override
  Widget build(BuildContext context) {
    Widget result = Listener(
      onPointerDown: _handlePointerDown,
      onPointerPanZoomStart: _handlePointerPanZoomStart,
      behavior: widget.behavior ?? _defaultBehavior,
      child: widget.child,
    );
    if (!widget.excludeFromSemantics) {
      result = _GestureSemantics(
        behavior: widget.behavior ?? _defaultBehavior,
        assignSemantics: _updateSemanticsForRenderObject,
        child: result,
      );
    }
    return result;
  }

在我们的示例中其实只监听了onPointerDown事件,其实现_handlePointerDown代码如下:

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

遍历_recognizers,执行recognizer.addPointer(event)

void addPointer(PointerDownEvent event) {
    _pointerToKind[event.pointer] = event.kind;
    if (isPointerAllowed(event)) {
      addAllowedPointer(event);
    } else {
      handleNonAllowedPointer(event);
    }
  }

现在的场景中isPointerAllowed()一定为true,执行 addAllowedPointer(event)

@protected
  void addAllowedPointer(PointerDownEvent event) { }

它是个抽象方法,我们看看子类BaseTapGestureRecognizer的实现:

@override
  void addAllowedPointer(PointerDownEvent event) {
    assert(event != null);
    if (state == GestureRecognizerState.ready) {
      if (_down != null && _up != null) {
        assert(_down!.pointer == _up!.pointer);
        _reset();
      }
      _down = event;
    }
    if (_down != null) {
      super.addAllowedPointer(event);
    }
  }

实际上调的就是super的方法,即PrimaryPointerGestureRecognizeraddAllowedPointer(event)

@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));
      }
    }
  }

继续调super,即OneSequenceGestureRecognizeraddAllowedPointer(event)

@override
  @protected
  void addAllowedPointer(PointerDownEvent event) {
    startTrackingPointer(event.pointer, event.transform);
  }

继续追踪startTrackingPointer()的实现:

@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);
  }

首先就是调用PointerRouteraddRoute()方法,这个是不是很熟悉?

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;
  }

它的实现就是往我们之前章节提到的_routeMap添加成员,等待分发手势事件后的回调。然后调用_addPointerToArena()。这个方法从命名中就可得知,它是往手势竞技场中添加成员:

GestureArenaEntry _addPointerToArena(int pointer) {
    if (_team != null) {
      return _team!.add(pointer, this);
    }
    return GestureBinding.instance.gestureArena.add(pointer, this);
  }
GestureArenaEntry add(int pointer, GestureArenaMember member) {
    final _GestureArena state = _arenas.putIfAbsent(pointer, () {
      assert(_debugLogDiagnostic(pointer, '★ Opening new gesture arena.'));
      return _GestureArena();
    });
    state.add(member);
    assert(_debugLogDiagnostic(pointer, 'Adding: $member'));
    return GestureArenaEntry._(this, pointer, member);
  }

好的现在在接收到PointerDownEvent时,onTap相应的GestureArenaMember已经进入手势竞技场了,收到任何手势事件都会执行之前章节分析过的:针对每一个target,执行PointerRoute.route(event),这个方法的作用是将手势事件首先分发给PointerRouter_routeMap的各个成员去处理。其实现在各个GestureArenaMemberhandleEvent()中,我们来看PrimaryPointerGestureRecognizerhandleEvent()的实现:

@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);
  }

这段代码实际上是先计算一下,如果接收到的是PointerMoveEvent事件时,移动距离是不是足够小,如果是的话,即可以继续处理onTap事件,会调用 handlePrimaryPointer(event)

@protected
  void handlePrimaryPointer(PointerEvent event);

其实现在BaseTapGestureRecognizer

@override
  void handlePrimaryPointer(PointerEvent event) {
    if (event is PointerUpEvent) {
      _up = event;
      _checkUp();
    } else if (event is PointerCancelEvent) {
      resolve(GestureDisposition.rejected);
      if (_sentTapDown) {
        _checkCancel(event, '');
      }
      _reset();
    } else if (event.buttons != _down!.buttons) {
      resolve(GestureDisposition.rejected);
      stopTrackingPointer(primaryPointer!);
    }
  }

由于目前我们还在处理PointerDownEvent事件,所以不会做任何事情。到此为止,接收到PointerDownEvent事件时,最重要的事情就是将TapGestureRecognizer添加到手势竞技场等待。在只监听onTap事件的时候,会判断接收到PointerMoveEvent时的移动距离,如果足够小的话会继续等待。接下来,我们手势抬起,接收PointerUpEvent,我们看看会发生什么。由于PrimaryPointerGestureRecognizerhandleEvent()PointerUpEvent并没有做什么,我们回到GestureBindinghandleEvent()

@override // from HitTestTarget
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    pointerRouter.route(event);
    if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
      gestureArena.close(event.pointer);
    } else if (event is PointerUpEvent || event is PointerPanZoomEndEvent) {
      gestureArena.sweep(event.pointer);
    } else if (event is PointerSignalEvent) {
      pointerSignalResolver.resolve(event);
    }
  }

之前章节分析过,PointerUpEvent是会走gestureArena.sweep(event.pointer)清扫手势竞技场:

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);
      }
    }
  }

由于当前竞技场中只有TapGestureRecognizer这一个成员,会执行到 state.members.first.acceptGesture(pointer)宣告胜利。acceptGesture()的实现在BaseTapGestureRecognizer中:

//BaseTapGestureRecognizer
  @override
  void acceptGesture(int pointer) {
    super.acceptGesture(pointer);
    if (pointer == primaryPointer) {
      _checkDown();
      _wonArenaForPrimaryPointer = true;
      _checkUp();
    }
  }
//PrimaryPointerGestureRecognizer
  @override
  void acceptGesture(int pointer) {
    if (pointer == primaryPointer) {
      _stopTimer();
      _gestureAccepted = true;
    }
  }

先调用superacceptGesture()方法,停止计时器。然后调用_checkDown()_checkUp(),给业务层相应的回调去处理。同时设置 _wonArenaForPrimaryPointertrue标记胜利。所以其实在单击事件中,downup事件是在手势抬起收到PointerUpEvent处理完成后才回调给用户进行处理的。到此为止,我们使用GestureDetector监听onTap事件的流程就分析完了。那么现在我们增加针对onLongPress的监听,看看手势竞技场是怎么处理的。

onLongPress 

我们回到GestureDetectorbuild()方法:

if (onLongPressDown != null ||
        onLongPressCancel != null ||
        onLongPress != null ||
        onLongPressStart != null ||
        onLongPressMoveUpdate != null ||
        onLongPressUp != null ||
        onLongPressEnd != null ||
        onSecondaryLongPressDown != null ||
        onSecondaryLongPressCancel != null ||
        onSecondaryLongPress != null ||
        onSecondaryLongPressStart != null ||
        onSecondaryLongPressMoveUpdate != null ||
        onSecondaryLongPressUp != null ||
        onSecondaryLongPressEnd != null ||
        onTertiaryLongPressDown != null ||
        onTertiaryLongPressCancel != null ||
        onTertiaryLongPress != null ||
        onTertiaryLongPressStart != null ||
        onTertiaryLongPressMoveUpdate != null ||
        onTertiaryLongPressUp != null ||
        onTertiaryLongPressEnd != null) {
      gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
        () => LongPressGestureRecognizer(debugOwner: this),
        (LongPressGestureRecognizer instance) {
          instance
            ..onLongPressDown = onLongPressDown
            ..onLongPressCancel = onLongPressCancel
            ..onLongPress = onLongPress
            ..onLongPressStart = onLongPressStart
            ..onLongPressMoveUpdate = onLongPressMoveUpdate
            ..onLongPressUp = onLongPressUp
            ..onLongPressEnd = onLongPressEnd
            ..onSecondaryLongPressDown = onSecondaryLongPressDown
            ..onSecondaryLongPressCancel = onSecondaryLongPressCancel
            ..onSecondaryLongPress = onSecondaryLongPress
            ..onSecondaryLongPressStart = onSecondaryLongPressStart
            ..onSecondaryLongPressMoveUpdate = onSecondaryLongPressMoveUpdate
            ..onSecondaryLongPressUp = onSecondaryLongPressUp
            ..onSecondaryLongPressEnd = onSecondaryLongPressEnd
            ..onTertiaryLongPressDown = onTertiaryLongPressDown
            ..onTertiaryLongPressCancel = onTertiaryLongPressCancel
            ..onTertiaryLongPress = onTertiaryLongPress
            ..onTertiaryLongPressStart = onTertiaryLongPressStart
            ..onTertiaryLongPressMoveUpdate = onTertiaryLongPressMoveUpdate
            ..onTertiaryLongPressUp = onTertiaryLongPressUp
            ..onTertiaryLongPressEnd = onTertiaryLongPressEnd
            ..gestureSettings = gestureSettings;
        },
      );
    }

在长按时会创建一个LongPressGestureRecognizer。我们看看LongPressGestureRecognizer的构造方法:

LongPressGestureRecognizer({
    Duration? duration,
    // TODO(goderbauer): remove ignore when https://github.com/dart-lang/linter/issues/3349 is fixed.
    // ignore: avoid_init_to_null
    super.postAcceptSlopTolerance = null,
    @Deprecated(
      'Migrate to supportedDevices. '
      'This feature was deprecated after v2.3.0-1.0.pre.',
    )
    super.kind,
    super.supportedDevices,
    super.debugOwner,
  }) : super(
         deadline: duration ?? kLongPressTimeout,
       );
const Duration kLongPressTimeout = Duration(milliseconds: 500);

super里传给父类PrimaryPointerGestureRecognizerdeadline默认是500ms,这个是触发长按事件的时长。我们回忆一下之前章节提到的,在GestureDetectorbuild()的时候,创建了一个Listener组件,监听其onPointerDown方法,onPointerDown的实现是_handlePointerDown(),其代码如下:

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

遍历_recognizers,执行recognizer.addPointer(event)

void addPointer(PointerDownEvent event) {
    _pointerToKind[event.pointer] = event.kind;
    if (isPointerAllowed(event)) {
      addAllowedPointer(event);
    } else {
      handleNonAllowedPointer(event);
    }
  }

由于LongPressGestureRecognizer没有实现addAllowedPointer()方法,我们看看addAllowedPointer()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不为空的话,启动一个500ms的Timer,到时间后调用 didExceedDeadlineWithEvent(event)方法:

@override
  void didExceedDeadline() {
    // Exceeding the deadline puts the gesture in the accepted state.
    resolve(GestureDisposition.accepted);
    _longPressAccepted = true;
    super.acceptGesture(primaryPointer!);
    _checkLongPressStart();
  }

会在此时调用acceptGesture()宣告LongPressGestureRecognizer胜利,并回调onLongPress给业务层进行处理。而不会等待PointerUpEvent再去判定胜利者。给业务层的体验就是,长按时,不需手势抬起,就能收到onLongPress回调。好了onLongPress是怎么处理的我们也分析完了,我们继续分析示例中的onDoubleTap双击事件的处理。

onDoubleTap 

我们还是回到GestureDetectorbuild()方法:

if (onDoubleTap != null) {
      gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
        () => DoubleTapGestureRecognizer(debugOwner: this),
        (DoubleTapGestureRecognizer instance) {
          instance
            ..onDoubleTapDown = onDoubleTapDown
            ..onDoubleTap = onDoubleTap
            ..onDoubleTapCancel = onDoubleTapCancel
            ..gestureSettings = gestureSettings;
        },
      );
    }

创建了DoubleTapGestureRecognizer。关键处理在它的addAllowedPointer()方法里:

@override
  void addAllowedPointer(PointerDownEvent event) {
    if (_firstTap != null) {
      if (!_firstTap!.isWithinGlobalTolerance(event, kDoubleTapSlop)) {
        // Ignore out-of-bounds second taps.
        return;
      } else if (!_firstTap!.hasElapsedMinTime() || !_firstTap!.hasSameButton(event)) {
        // Restart when the second tap is too close to the first (touch screens
        // often detect touches intermittently), or when buttons mismatch.
        _reset();
        return _trackTap(event);
      } else if (onDoubleTapDown != null) {
        final TapDownDetails details = TapDownDetails(
          globalPosition: event.position,
          localPosition: event.localPosition,
          kind: getKindForPointer(event.pointer),
        );
        invokeCallback<void>('onDoubleTapDown', () => onDoubleTapDown!(details));
      }
    }
    _trackTap(event);
  }

一开始_firstTap一定是空,直接执行 _trackTap(event)方法:

void _trackTap(PointerDownEvent event) {
    _stopDoubleTapTimer();
    final _TapTracker tracker = _TapTracker(
      event: event,
      entry: GestureBinding.instance.gestureArena.add(event.pointer, this),
      doubleTapMinTime: kDoubleTapMinTime,
      gestureSettings: gestureSettings,
    );
    _trackers[event.pointer] = tracker;
    tracker.startTrackingPointer(_handleEvent, event.transform);
  }

创建一个_TapTracker对象,向手势竞技场注册自己然后调用tracker.startTrackingPointer(_handleEvent, event.transform)

void startTrackingPointer(PointerRoute route, Matrix4? transform) {
    if (!_isTrackingPointer) {
      _isTrackingPointer = true;
      GestureBinding.instance.pointerRouter.addRoute(pointer, route, transform);
    }
  }

route添加到_routeMap中,等待手势触发回调。我们继续按看看_handleEvent ()的实现:

void _handleEvent(PointerEvent event) {
    final _TapTracker tracker = _trackers[event.pointer]!;
    if (event is PointerUpEvent) {
      if (_firstTap == null) {
        _registerFirstTap(tracker);
      } else {
        _registerSecondTap(tracker);
      }
    } else if (event is PointerMoveEvent) {
      if (!tracker.isWithinGlobalTolerance(event, kDoubleTapTouchSlop)) {
        _reject(tracker);
      }
    } else if (event is PointerCancelEvent) {
      _reject(tracker);
    }
  }

主要关注PointerUpEvent事件的接收,因为双击事件就是判断两次PointerUpEvent事件是否符合双击的条件,我们先来看首次判断时_firstTap == null,调用_registerFirstTap(tracker)的情况:

void _registerFirstTap(_TapTracker tracker) {
    _startDoubleTapTimer();
    GestureBinding.instance.gestureArena.hold(tracker.pointer);
    // Note, order is important below in order for the clear -> reject logic to
    // work properly.
    _freezeTracker(tracker);
    _trackers.remove(tracker.pointer);
    _clearTrackers();
    _firstTap = tracker;
  }

先调用_startDoubleTapTimer(),启动双击的事件Timer,默认是300ms。然后调用了GestureBinding.instance.gestureArena.hold(tracker.pointer)

void hold(int pointer) {
    final _GestureArena? state = _arenas[pointer];
    if (state == null) {
      return; // This arena either never existed or has been resolved.
    }
    state.isHeld = true;
    assert(_debugLogDiagnostic(pointer, 'Holding', state));
  }

会将state.isHeld设置为true。然后我们回到GestureBindinghandleEvent()

@override // from HitTestTarget
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    pointerRouter.route(event);
    if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
      gestureArena.close(event.pointer);
    } else if (event is PointerUpEvent || event is PointerPanZoomEndEvent) {
      gestureArena.sweep(event.pointer);
    } else if (event is PointerSignalEvent) {
      pointerSignalResolver.resolve(event);
    }
  }

在接收到PointerUpEvent时执行gestureArena.sweep(event.pointer)

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.
    }
//...
  }

如果state.isHeldtrue,说明有事件在等待判定,不可以在此时宣布谁胜利,直接return。当收到第二个点击事件时,回到addAllowedPointer()

@override
  void addAllowedPointer(PointerDownEvent event) {
    if (_firstTap != null) {
      if (!_firstTap!.isWithinGlobalTolerance(event, kDoubleTapSlop)) {
        // Ignore out-of-bounds second taps.
        return;
      } else if (!_firstTap!.hasElapsedMinTime() || !_firstTap!.hasSameButton(event)) {
        // Restart when the second tap is too close to the first (touch screens
        // often detect touches intermittently), or when buttons mismatch.
        _reset();
        return _trackTap(event);
      } else if (onDoubleTapDown != null) {
        final TapDownDetails details = TapDownDetails(
          globalPosition: event.position,
          localPosition: event.localPosition,
          kind: getKindForPointer(event.pointer),
        );
        invokeCallback<void>('onDoubleTapDown', () => onDoubleTapDown!(details));
      }
    }
    _trackTap(event);
  }

如果两次点击时间小于双击阈值的话,重新执行 _trackTap(event)。我们回到DoubleTapGestureRecognizer_handleEvent ()

void _handleEvent(PointerEvent event) {
    final _TapTracker tracker = _trackers[event.pointer]!;
    if (event is PointerUpEvent) {
      if (_firstTap == null) {
        _registerFirstTap(tracker);
      } else {
        _registerSecondTap(tracker);
      }
    } else if (event is PointerMoveEvent) {
      if (!tracker.isWithinGlobalTolerance(event, kDoubleTapTouchSlop)) {
        _reject(tracker);
      }
    } else if (event is PointerCancelEvent) {
      _reject(tracker);
    }
  }

_firstTap不为空的情况,说明接收到第二个点击事件了,追踪_registerSecondTap()的实现:

void _registerSecondTap(_TapTracker tracker) {
    _firstTap!.entry.resolve(GestureDisposition.accepted);
    tracker.entry.resolve(GestureDisposition.accepted);
    _freezeTracker(tracker);
    _trackers.remove(tracker.pointer);
    _checkUp(tracker.initialButtons);
    _reset();
  }

两个手势事件都执行resolve(GestureDisposition.accepted)方法,这个方法其实就是宣告胜利或失败的,我们看看它的实现:

void resolve(GestureDisposition disposition) {
    _arena._resolve(_pointer, _member, disposition);
  }
void _resolve(int pointer, GestureArenaMember member, GestureDisposition disposition) {
    final _GestureArena? state = _arenas[pointer];
    if (state == null) {
      return; // This arena has already resolved.
    }
    assert(_debugLogDiagnostic(pointer, '${ disposition == GestureDisposition.accepted ? "Accepting" : "Rejecting" }: $member'));
    assert(state.members.contains(member));
    if (disposition == GestureDisposition.rejected) {
//...
    } else {
      assert(disposition == GestureDisposition.accepted);
      if (state.isOpen) {
        state.eagerWinner ??= member;
      } else {
        assert(_debugLogDiagnostic(pointer, 'Self-declared winner: $member'));
        _resolveInFavorOf(pointer, state, member);
      }
    }
  }

当传入GestureDisposition.accepted时,会执行_resolveInFavorOf()方法:

void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {
    assert(state == _arenas[pointer]);
    assert(state != null);
    assert(state.eagerWinner == null || state.eagerWinner == member);
    assert(!state.isOpen);
    _arenas.remove(pointer);
    for (final GestureArenaMember rejectedMember in state.members) {
      if (rejectedMember != member) {
        rejectedMember.rejectGesture(pointer);
      }
    }
    member.acceptGesture(pointer);
  }

sweep()之前,直接宣告胜利。到此为止,onDoubleTap的流程也分析完成了。之前的示例中,我们还剩下一个onVerticalDragDown没有分析。下面我们再分析一下onVerticalDragDown

onVerticalDragDown 

重新回到GestureDetectorbuild()方法:

if (onVerticalDragDown != null ||
        onVerticalDragStart != null ||
        onVerticalDragUpdate != null ||
        onVerticalDragEnd != null ||
        onVerticalDragCancel != null) {
      gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
        () => VerticalDragGestureRecognizer(debugOwner: this),
        (VerticalDragGestureRecognizer instance) {
          instance
            ..onDown = onVerticalDragDown
            ..onStart = onVerticalDragStart
            ..onUpdate = onVerticalDragUpdate
            ..onEnd = onVerticalDragEnd
            ..onCancel = onVerticalDragCancel
            ..dragStartBehavior= dragStartBehavior
            ..gestureSettings = gestureSettings;
        },
      );
    }

创建了VerticalDragGestureRecognizer对象,之后在down事件时会被加入到手势竞技场。VerticalDragGestureRecognizer没有实现handleEvent(),而是它的父类DragGestureRecognizer实现的:

@override
  void handleEvent(PointerEvent event) {
//...
    if (event is PointerMoveEvent || event is PointerPanZoomUpdateEvent) {
      final Offset delta = (event is PointerMoveEvent) ? event.delta : (event as PointerPanZoomUpdateEvent).panDelta;
      final Offset localDelta = (event is PointerMoveEvent) ? event.localDelta : (event as PointerPanZoomUpdateEvent).localPanDelta;
      final Offset position = (event is PointerMoveEvent) ? event.position : (event.position + (event as PointerPanZoomUpdateEvent).pan);
      final Offset localPosition = (event is PointerMoveEvent) ? event.localPosition : (event.localPosition + (event as PointerPanZoomUpdateEvent).localPan);
      if (_state == _DragState.accepted) {
        _checkUpdate(
          sourceTimeStamp: event.timeStamp,
          delta: _getDeltaForDetails(localDelta),
          primaryDelta: _getPrimaryValueFromOffset(localDelta),
          globalPosition: position,
          localPosition: localPosition,
        );
      } else {
        _pendingDragOffset += OffsetPair(local: localDelta, global: delta);
        _lastPendingEventTimestamp = event.timeStamp;
        _lastTransform = event.transform;
        final Offset movedLocally = _getDeltaForDetails(localDelta);
        final Matrix4? localToGlobalTransform = event.transform == null ? null : Matrix4.tryInvert(event.transform!);
        _globalDistanceMoved += PointerEvent.transformDeltaViaPositions(
          transform: localToGlobalTransform,
          untransformedDelta: movedLocally,
          untransformedEndPosition: localPosition
        ).distance * (_getPrimaryValueFromOffset(movedLocally) ?? 1).sign;
        if (_hasSufficientGlobalDistanceToAccept(event.kind, gestureSettings?.touchSlop)) {
          resolve(GestureDisposition.accepted);
        }
      }
    }
    if (event is PointerUpEvent || event is PointerCancelEvent || event is PointerPanZoomEndEvent) {
      _giveUpPointer(event.pointer);
    }
  }

我们截取了部分重要的代码,在PointerMoveEvent阶段,会一直计算_globalDistanceMoved的值,_hasSufficientGlobalDistanceToAccept()用来判断移动的距离是否大于固定的阈值:

@override
  bool _hasSufficientGlobalDistanceToAccept(PointerDeviceKind pointerDeviceKind, double? deviceTouchSlop) {
    return _globalDistanceMoved.abs() > computeHitSlop(pointerDeviceKind, gestureSettings);
  }

如果是的话,执行resolve(GestureDisposition.accepted),即在move阶段宣告胜利。

手势事件拦截

我们在开发过程中有时候需要对手势事件进行拦截,这个是怎么实现的呢?我们回想一下第三章讲解的hitTest(),优先对子进行命中测试,如果命中即由子来实现手势事件。这么看来拦截的思路就是不要让子进行hitTest()Flutter中的AbsorbPointer就是来干这件事情的。我们来看一下它的关键处理:

@override
  bool hitTest(BoxHitTestResult result, { required Offset position }) {
    return absorbing
        ? size.contains(position)
        : super.hitTest(result, position: position);
  }

重写hitTest(),如果absorbingtrue,那么直接判断手势事件的position是否已经在AbsorbPointer的范围内,不会再执行super.hitTest(result, position: position)对子来判定hitTest()了。

总结

先上一张图,简单的说明一下Flutter处理手势事件的流程:

dad0629dc06e7f5ba1ae193159fd2668.jpeg

Flutter对于手势事件的处理流程的思路其实非常直观,在down事件时通过hitTest()确定可能触发手势事件的Widget并给它们设置优先级,再调用dispatchEvent对手势事件进行分发并向竞技场注册成员等待判定。在handleEvent()时会根据条件解决冲突判定手势的胜利者。本文通过源码分析对Flutter的手势事件分发与冲突处理进行了说明,同时通过GestureDetector的示例分析了不同冲突的具体处理方式,以及如何对手势事件进行拦截,希望对大家理解Flutter的手势事件有所帮助。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值