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

news/2024/10/27 6:26:34/

 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()初始化的实现:

@overridevoid 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.dartvoid _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()

@overridevoid 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 HitTestablevoid hitTest(HitTestResult result, Offset position) {result.add(HitTestEntry(this));}

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

@overridevoid 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()的实现:

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

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

@overridebool 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()的实现:

@protectedbool hitTestSelf(Offset position) => false;

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

@overridebool 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 HitTestTargetvoid 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是什么呢?我们举个例子:

@overrideWidget 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 HitTestTargetvoid 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

我们举个例子:

@overrideWidget 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

@overrideWidget 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()的实现:

@overridevoid 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()的实现:

@overrideWidget 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的实现:

@overrideRenderPointerListener 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()的实现:

@overridevoid 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()

@overrideWidget 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)

@protectedvoid addAllowedPointer(PointerDownEvent event) { }

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

@overridevoid 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)

@overridevoid 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@protectedvoid addAllowedPointer(PointerDownEvent event) {startTrackingPointer(event.pointer, event.transform);}

继续追踪startTrackingPointer()的实现:

@protectedvoid 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()的实现:

@overridevoid 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)

@protectedvoid handlePrimaryPointer(PointerEvent event);

其实现在BaseTapGestureRecognizer

@overridevoid 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 HitTestTargetvoid 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@overridevoid acceptGesture(int pointer) {super.acceptGesture(pointer);if (pointer == primaryPointer) {_checkDown();_wonArenaForPrimaryPointer = true;_checkUp();}}
//PrimaryPointerGestureRecognizer@overridevoid 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_nullsuper.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的实现:

@overridevoid 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)方法:

@overridevoid 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()方法里:

@overridevoid 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 HitTestTargetvoid 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()

@overridevoid 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实现的:

@overridevoid 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()用来判断移动的距离是否大于固定的阈值:

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

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

手势事件拦截

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

@overridebool 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的手势事件有所帮助。


http://www.ppmy.cn/news/162289.html

相关文章

寿康宝鉴

【原文】 5.周思敏曰&#xff1a;人生天地间&#xff0c;圣贤豪杰&#xff0c;唯其所为。然须有十分精神&#xff0c;方做得十分事业&#xff0c;苟不知节欲&#xff0c;以保守精神&#xff0c;虽有绝大志量&#xff0c;神昏力倦&#xff0c;未有不半途而废者。 欲火焚烧&#…

【shiro】shiro整合JWT——2.如何整合

前言 shiro整合JWT系列&#xff0c;主要记录核心思路–如何在shiroredis整合JWTToken。 上一篇中&#xff0c;我们知道了需要创建JwtToken、JwtUtil、JwtFilter。 该篇主要讲如何在shiro框架中&#xff0c;配置Jwt。 ps&#xff1a;本文主要以记录核心思路为主。 1、ShiroCon…

python PyAutoGUI 使用

pip install pyautoguidocument import pyautoguipyautogui.size() pyautogui.position()鼠标 To 是绝对坐标Rel 或者 不带 To 是相对坐标 pyautogui.moveTo(xNone, yNone, duration0.0) pyautogui.dragTo(xNone, yNone, duration0.0, buttonPRIMARY)click(xNone, yNone, cl…

uni-app之Cover-View组件详细使用教程

在 UniApp 中&#xff0c;Cover-View 组件是一种用于展示覆盖在页面上方的视图元素的组件。它可以用于创建各种遮罩、弹出层、悬浮按钮等效果&#xff0c;提供了更多自定义样式和交互的可能性。本教程将详细介绍 Cover-View 组件的用法和示例代码。 步骤1&#xff1a;创建一个…

宾得67中画幅相机红色版套装现身eBay

本文来自新摄影 近日&#xff0c;卖家shueido在eBay上正在销售一款红色宾得67中画幅相机套装。值得注意的是&#xff0c;这款套机机身颜色并非原装设计&#xff0c;而是玩家后期改装。据悉&#xff0c;eBay在售的这套宾得67相机套装包括宾得67机身、TTL取景器、105mm F2.4镜头以…

clang 01. clang driver流程分析

文章目录 前言在这里简要概述一下clang的流程 1.clang driver代码分析1.1创建诊断&#xff08;DIagnosticsEngine&#xff09;实例1.2创建Driver(clang::driver::Driver)的实例1.3通过Driver的BuildCompilation方法生成需要执行的命令1.4Jobs构建完成&#xff0c;通过Driver的E…

Python3中对时间的处理(持续更新ing...)

诸神缄默不语-个人CSDN博文目录 本文介绍Python3中各种处理时间的库和使用方案 最近更新时间&#xff1a;2023.6.2 最早更新时间&#xff1a;2023.6.2 文章目录 1. datetime库2. time库3. JioNLP库&#xff1a;&#xff08;中文&#xff09;从文本中提取时间信息4. datefinde…

深入了解JavaScript中的Promise

在JavaScript中&#xff0c;异步编程是必不可少的。过去&#xff0c;我们通常使用回调函数来处理异步操作&#xff0c;但回调地狱&#xff08;callback hell&#xff09;和复杂的错误处理使得代码难以维护。为了解决这些问题&#xff0c;ES6引入了Promise&#xff0c;它是一种更…