本文字数: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
,我们回到GestureBinding
的initInstances()
:
@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);}}
这段代码的整体思路是:
如果
event
是PointerDownEvent
等四种event
之一时(我们假设创建的是一个移动端app,这四种event
只考虑PointerDownEvent
的情况),创建一个HitTestResult
对象,并调用hitTest()
方法,然后将hitTestResult
赋值给_hitTests[event.pointer]
;如果是
PointerUpEvent
或PointerCancelEvent
,那么将此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
中,HitTestResult
的target
对象通常就是一个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
中。由于mixin
,RendererBinding
是GesturesBinding
的子类,而RendererBinding
实现了hitTest()
方法,所以我们看看RendererBinding
的hitTest()
的实现:
@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()
之前,先调用了renderView
的hitTest()
方法,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
,如果是的话需要先执行child
的hitTest()
方法,再将自己封装成一个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;}
其中position
是PointerEvent
的position
,childParentData
记录了子相对于父的偏移量。执行result.addWithPaintOffset()
方法,其中hitTest
是个callback
,执行的是child!.hitTest(result, position: transformed)
。也就是说hitTestChildren()
方法是遍历整个RenderObject
树,递归执行 child!.hitTest()
方法,去判断子是否有命中手势事件。如果已经没有子或者子没有命中的话,才会判断自己是否命中,我们回过头来看看hitTestSelf()
的实现:
@protectedbool hitTestSelf(Offset position) => false;
它也是个抽象方法,假设我们点击的是个Image
,它所对应的RenderObject
是RenderImage
,其hitTestSelf()
的实现如下:
@overridebool hitTestSelf(Offset position) => true;
只要手势事件的position
在RenderImage
的_size
范围内,就命中手势事件。到此为止,hitTest
的过程我们就梳理完成了。总结一下,hitTest
实际上就是判断当前Widget
对应的RenderObject
是否命中手势事件。
在移动端只有
PointerDownEvent
事件会进行命中测试;遍历整个
RenderObject
树,优先对子进行命中测试,若子命中即先添加进HitTestResult
中;若子没有命中,则判断自己是否命中;
只要子或者自己命中,都会加到
HitTestResult
中。
我们将hitTest()
的处理流程总结成流程图如下:
在命中测试完成得到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
监听onTap
和onLongPress
事件。代码运行后我们都知道结果:单击Text
会打印onTap
的log
,长按Text
会打印onLongPress
的log
。具体的原因之后我们再详细分析,先说个结论:onTap
和onLongPress
将会注册两个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 == 1
为true
,则调用_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)
是个抽象方法,之后我们再举例说明它的实现。如果注册了多个PointerRoute
且state.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.isHeld
为true
,说明竞技场里有的成员生命周期比较长,需要等待,所以先不做处理。比方说上面的例子中onDoubleTap
事件就会设置state.isHeld
为true
。否则如将会宣告竞技场中第一个加入的成员胜利,其他成员失败。到此为止,GestureBinding
中的dispatchEvent()
就分析完毕了,我们总结一下:
先遍历
HitTestEntry
,执行entry.target.handleEvent()
方法,顺序是子优先;针对每一个
target
,执行PointerRoute.route(event)
,这个方法的作用是将手势事件首先分发给PointerRouter
里_routeMap
的各个成员去处理;在
PointerDownEvent
的时候关闭手势竞技场,并根据条件判定当前就可决定的胜利成员;在
PointerUpEvent
时清扫手势竞技场,并最终判定胜利的成员。
我们将dispatchEvent()
的处理流程总结成流程图如下:
到此为止,我们知道了手势事件是怎么分发和解决冲突的。接下来我们通过分析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
竞技场成员对象。然后返回一个RawGestureDetector
的Widget
。RawGestureDetector
是个StatefulWidget
,我们看看它对应的RawGestureDetectorState
中initState()
的实现:
@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
对象。所以我们看看RenderPointerListener
里handleEvent()
的实现:
@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);}}
它非常的简单,就是将基础的手势事件回调回去。我们回到RawGestureDetectorState
的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;}
在我们的示例中其实只监听了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
的方法,即PrimaryPointerGestureRecognizer
的addAllowedPointer(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
,即OneSequenceGestureRecognizer
的addAllowedPointer(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);}
首先就是调用PointerRouter
的addRoute()
方法,这个是不是很熟悉?
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
的各个成员去处理。其实现在各个GestureArenaMember
的handleEvent()
中,我们来看PrimaryPointerGestureRecognizer
对handleEvent()
的实现:
@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
,我们看看会发生什么。由于PrimaryPointerGestureRecognizer
的handleEvent()
对PointerUpEvent
并没有做什么,我们回到GestureBinding
的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);}}
之前章节分析过,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;}}
先调用super
的acceptGesture()
方法,停止计时器。然后调用_checkDown()
和_checkUp()
,给业务层相应的回调去处理。同时设置 _wonArenaForPrimaryPointer
为true
标记胜利。所以其实在单击事件中,down
和up
事件是在手势抬起收到PointerUpEvent
处理完成后才回调给用户进行处理的。到此为止,我们使用GestureDetector
监听onTap
事件的流程就分析完了。那么现在我们增加针对onLongPress
的监听,看看手势竞技场是怎么处理的。
onLongPress
我们回到GestureDetector
的build()
方法:
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
里传给父类PrimaryPointerGestureRecognizer
的deadline
默认是500ms,这个是触发长按事件的时长。我们回忆一下之前章节提到的,在GestureDetector
走build()
的时候,创建了一个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
我们还是回到GestureDetector
的build()
方法:
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
。然后我们回到GestureBinding
的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);}}
在接收到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.isHeld
为true
,说明有事件在等待判定,不可以在此时宣布谁胜利,直接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
重新回到GestureDetector
的build()
方法:
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()
,如果absorbing
为true
,那么直接判断手势事件的position
是否已经在AbsorbPointer
的范围内,不会再执行super.hitTest(result, position: position)
对子来判定hitTest()
了。
总结
先上一张图,简单的说明一下Flutter
处理手势事件的流程:
Flutter
对于手势事件的处理流程的思路其实非常直观,在down
事件时通过hitTest()
确定可能触发手势事件的Widget
并给它们设置优先级,再调用dispatchEvent
对手势事件进行分发并向竞技场注册成员等待判定。在handleEvent()
时会根据条件解决冲突判定手势的胜利者。本文通过源码分析对Flutter
的手势事件分发与冲突处理进行了说明,同时通过GestureDetector
的示例分析了不同冲突的具体处理方式,以及如何对手势事件进行拦截,希望对大家理解Flutter
的手势事件有所帮助。