本文字数:43617字
预计阅读时间:110分钟
前言
之前有两篇文章都围绕着runApp()
进行展开,讲解了布局绘制的详细过程。
https://www.jianshu.com/p/2ef749ff4d40/
https://www.jianshu.com/p/f37f8da235ec
那么接下来我们想详细的说一说Flutter
是如何处理手势事件的。本文将通过源码详细分析Flutter
的事件分发与冲突处理过程,并通过示例说明不同冲突的处理方式。本文的组织架构如下:
手势事件的初始化
命中测试
PointerEvent的封装
hitTest()
dispatchEvent()
GestureDetector
onTap
onLongPress
onDoubleTap
onVerticalDragDown
手势事件拦截
总结
手势事件的初始化
还是先回到我们熟悉的runApp()
:
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
前面的文章介绍过WidgetsFlutterBinding
混合了很多mixin
:
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding
手势相关的是GestureBinding
,我们来看看它的initInstances()
初始化的实现:
@override
void initInstances() {
super.initInstances();
_instance = this;
platformDispatcher.onPointerDataPacket = _handlePointerDataPacket;
}
为platformDispatcher
注册了onPointerDataPacket
回调,其实现是_handlePointerDataPacket()
。我们先追踪一下onPointerDataPacket
的回调时机:
PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket;
PointerDataPacketCallback? _onPointerDataPacket;
Zone _onPointerDataPacketZone = Zone.root;
set onPointerDataPacket(PointerDataPacketCallback? callback) {
_onPointerDataPacket = callback;
_onPointerDataPacketZone = Zone.current;
}
继续追踪onPointerDataPacket
的调用时机:
// Called from the engine, via hooks.dart
void _dispatchPointerDataPacket(ByteData packet) {
if (onPointerDataPacket != null) {
_invoke1<PointerDataPacket>(
onPointerDataPacket,
_onPointerDataPacketZone,
_unpackPointerDataPacket(packet),
);
}
}
void _invoke1<A>(void Function(A a)? callback, Zone zone, A arg) {
if (callback == null) {
return;
}
assert(zone != null);
if (identical(zone, Zone.current)) {
callback(arg);
} else {
zone.runUnaryGuarded<A>(callback, arg);
}
}
@pragma('vm:entry-point')
void _dispatchPointerDataPacket(ByteData packet) {
PlatformDispatcher.instance._dispatchPointerDataPacket(packet);
}
从注释上我们就可得知,_dispatchPointerDataPacket()
就是接收从framework
层传递的手势事件,并进行处理的时机。_invoke1()
其实就是调用onPointerDataPacket
并将_unpackPointerDataPacket()
的返回值回调。其中packet
是一组未经处理的的ByteData
,通过_unpackPointerDataPacket()
方法对其进行解包处理,生成手势事件所需的实体类PointerData
:
static PointerDataPacket _unpackPointerDataPacket(ByteData packet) {
const int kStride = Int64List.bytesPerElement;
const int kBytesPerPointerData = _kPointerDataFieldCount * kStride;
final int length = packet.lengthInBytes ~/ kBytesPerPointerData;
assert(length * kBytesPerPointerData == packet.lengthInBytes);
final List<PointerData> data = <PointerData>[];
for (int i = 0; i < length; ++i) {
int offset = i * _kPointerDataFieldCount;
data.add(PointerData(
embedderId: packet.getInt64(kStride * offset++, _kFakeHostEndian),
timeStamp: Duration(microseconds: packet.getInt64(kStride * offset++, _kFakeHostEndian)),
//...
scale: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
rotation: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
));
assert(offset == (i + 1) * _kPointerDataFieldCount);
}
return PointerDataPacket(data: data);
}
好了到此为止我们可以得知,在启动app后,由GestureBinding
注册手势的回调事件,当engine
层发送来手势事件后,由PlatformDispatcher
封装成PointerData
实体,最终回调至GestureBinding
进行处理,接下来我们看接收到手势事件后的处理流程。
命中测试
PointerEvent的封装
在真正处理手势之前,第一步就是将手势事件封装成业务可用的PointerEvent
,我们回到GestureBinding
的initInstances()
:
@override
void initInstances() {
super.initInstances();
_instance = this;
platformDispatcher.onPointerDataPacket = _handlePointerDataPacket;
}
跟踪_handlePointerDataPacket
的实现:
void _handlePointerDataPacket(ui.PointerDataPacket packet) {
// We convert pointer data to logical pixels so that e.g. the touch slop can be
// defined in a device-independent manner.
_pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
if (!locked) {
_flushPointerEventQueue();
}
}
首先就是把解包后的packet.data
通过PointerEventConverter.expand()
再做次转换:
static Iterable<PointerEvent> expand(Iterable<ui.PointerData> data, double devicePixelRatio) {
return data
.where((ui.PointerData datum) => datum.signalKind != ui.PointerSignalKind.unknown)
.map((ui.PointerData datum) {
//...
switch (datum.signalKind ?? ui.PointerSignalKind.none) {
case ui.PointerSignalKind.none:
switch (datum.change) {
//...
case ui.PointerChange.down:
return PointerDownEvent(
timeStamp: timeStamp,
pointer: datum.pointerIdentifier,
kind: kind,
device: datum.device,
position: position,
buttons: _synthesiseDownButtons(datum.buttons, kind),
obscured: datum.obscured,
pressure: datum.pressure,
//...
);
case ui.PointerChange.move:
return PointerMoveEvent(
timeStamp: timeStamp,
pointer: datum.pointerIdentifier,
kind: kind,
device: datum.device,
position: position,
delta: delta,
buttons: _synthesiseDownButtons(datum.buttons, kind),
obscured: datum.obscured,
//...
);
//...
case ui.PointerSignalKind.unknown:
default: // ignore: no_default_cases, to allow adding a new [PointerSignalKind]
// TODO(moffatman): Remove after landing https://github.com/flutter/engine/pull/34402
// This branch should already have 'unknown' filtered out, but
// we don't want to return anything or miss if someone adds a new
// enumeration to PointerSignalKind.
throw StateError('Unreachable');
}
});
}
截取了部分代码,大致就是将packet.data
转换成PointerEvent
,并添加到_pendingPointerEvents
列表中。接下来我们看_flushPointerEventQueue()
的实现:
void _flushPointerEventQueue() {
assert(!locked);
while (_pendingPointerEvents.isNotEmpty) {
handlePointerEvent(_pendingPointerEvents.removeFirst());
}
}
void handlePointerEvent(PointerEvent event) {
assert(!locked);
if (resamplingEnabled) {
_resampler.addOrDispatch(event);
_resampler.sample(samplingOffset, _samplingClock);
return;
}
// Stop resampler if resampling is not enabled. This is a no-op if
// resampling was never enabled.
_resampler.stop();
_handlePointerEventImmediately(event);
}
void _handlePointerEventImmediately(PointerEvent event) {
HitTestResult? hitTestResult;
if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent || event is PointerPanZoomStartEvent) {
assert(!_hitTests.containsKey(event.pointer));
hitTestResult = HitTestResult();
hitTest(hitTestResult, event.position);
if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
_hitTests[event.pointer] = hitTestResult;
}
//...
} else if (event is PointerUpEvent || event is PointerCancelEvent || event is PointerPanZoomEndEvent) {
hitTestResult = _hitTests.remove(event.pointer);
} else if (event.down || event is PointerPanZoomUpdateEvent) {
hitTestResult = _hitTests[event.pointer];
}
//...
if (hitTestResult != null ||
event is PointerAddedEvent ||
event is PointerRemovedEvent) {
assert(event.position != null);
dispatchEvent(event, hitTestResult);
}
}
这段代码的整体思路是:
如果
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 HitTestable
void hitTest(HitTestResult result, Offset position) {
result.add(HitTestEntry(this));
}
创建一个HitTestEntry
对象并添加到HitTestResult
中。由于mixin
,RendererBinding
是GesturesBinding
的子类,而RendererBinding
实现了hitTest()
方法,所以我们看看RendererBinding
的hitTest()
的实现:
@override
void hitTest(HitTestResult result, Offset position) {
assert(renderView != null);
assert(result != null);
assert(position != null);
renderView.hitTest(result, position: position);
super.hitTest(result, position);
}
在调用super.hitTest()
之前,先调用了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()
的实现:
@protected
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) => false;
hitTestChildren()
是个抽象方法,由子类实现。我们举个例子,假设当前Widget
是个Align
,它所对应的RenderObject
对象其实是个RenderShiftedBox
,它的hitTestChildren()
实现如下:
@override
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
if (child != null) {
final BoxParentData childParentData = child!.parentData! as BoxParentData;
return result.addWithPaintOffset(
offset: childParentData.offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(transformed == position - childParentData.offset);
return child!.hitTest(result, position: transformed);
},
);
}
return false;
}
bool addWithPaintOffset({
required Offset? offset,
required Offset position,
required BoxHitTest hitTest,
}) {
assert(position != null);
assert(hitTest != null);
final Offset transformedPosition = offset == null ? position : position - offset;
if (offset != null) {
pushOffset(-offset);
}
final bool isHit = hitTest(this, transformedPosition);
if (offset != null) {
popTransform();
}
return isHit;
}
其中position
是PointerEvent
的position
,childParentData
记录了子相对于父的偏移量。执行result.addWithPaintOffset()
方法,其中hitTest
是个callback
,执行的是child!.hitTest(result, position: transformed)
。也就是说hitTestChildren()
方法是遍历整个RenderObject
树,递归执行 child!.hitTest()
方法,去判断子是否有命中手势事件。如果已经没有子或者子没有命中的话,才会判断自己是否命中,我们回过头来看看hitTestSelf()
的实现:
@protected
bool hitTestSelf(Offset position) => false;
它也是个抽象方法,假设我们点击的是个Image
,它所对应的RenderObject
是RenderImage
,其hitTestSelf()
的实现如下:
@override
bool 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 HitTestTarget
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event);
if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
gestureArena.close(event.pointer);
} else if (event is PointerUpEvent || event is PointerPanZoomEndEvent) {
gestureArena.sweep(event.pointer);
} else if (event is PointerSignalEvent) {
pointerSignalResolver.resolve(event);
}
}
首先会执行 pointerRouter.route(event)
,这个方法的作用是将手势事件首先分发给各个PointerRoute
去处理:
void route(PointerEvent event) {
final Map<PointerRoute, Matrix4?>? routes = _routeMap[event.pointer];
final Map<PointerRoute, Matrix4?> copiedGlobalRoutes = Map<PointerRoute, Matrix4?>.of(_globalRoutes);
if (routes != null) {
_dispatchEventToRoutes(
event,
routes,
Map<PointerRoute, Matrix4?>.of(routes),
);
}
_dispatchEventToRoutes(event, _globalRoutes, copiedGlobalRoutes);
}
_routeMap
是什么呢?我们举个例子:
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MaterialApp(
home: Scaffold(
body: Center(
child: GestureDetector(
onTap: () {
print("onTap");
},
onLongPress: () {
print("onLongPress");
},
child: const Text("GestureDetector test"),
),
),
),
);
}
使用GestureDetector
监听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 HitTestTarget
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event);
if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
gestureArena.close(event.pointer);
} else if (event is PointerUpEvent || event is PointerPanZoomEndEvent) {
gestureArena.sweep(event.pointer);
} else if (event is PointerSignalEvent) {
pointerSignalResolver.resolve(event);
}
}
如果是PointerDownEvent
,则执行gestureArena.close(event.pointer)
,如果是PointerUpEvent
,则执行gestureArena.sweep(event.pointer)
。这时候我们就有个疑问,gestureArena
是什么?gestureArena
是个GestureArenaManager
对象,是个手势竞技场管理类,它有个成员变量_arenas
:
final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};
_GestureArena
记录了所有手势竞技成员members
:
final List<GestureArenaMember> members = <GestureArenaMember>[];
之前例子中我们提到的单击和长按其实各自都会被封装成一个GestureArenaMember
,而GestureArenaManager
的作用就是判定竞技场中GestureArenaMember
的胜负,即最终响应的是什么事件。我们再回到handleEvent()
方法分析gestureArena.close(event.pointer)
的实现:
void close(int pointer) {
final _GestureArena? state = _arenas[pointer];
if (state == null) {
return; // This arena either never existed or has been resolved.
}
state.isOpen = false;
assert(_debugLogDiagnostic(pointer, 'Closing', state));
_tryToResolveArena(pointer, state);
}
在PointerDownEvent
的时候,会分发手势事件给所有的PointerRoute
,待PointerRoute
都注册完成后,关闭手势竞技场,调用_tryToResolveArena(pointer, state)
:
void _tryToResolveArena(int pointer, _GestureArena state) {
assert(_arenas[pointer] == state);
assert(!state.isOpen);
if (state.members.length == 1) {
scheduleMicrotask(() => _resolveByDefault(pointer, state));
} else if (state.members.isEmpty) {
_arenas.remove(pointer);
assert(_debugLogDiagnostic(pointer, 'Arena empty.'));
} else if (state.eagerWinner != null) {
assert(_debugLogDiagnostic(pointer, 'Eager winner: ${state.eagerWinner}'));
_resolveInFavorOf(pointer, state, state.eagerWinner!);
}
}
这个方法的作用是确定竞技场里的哪个GestureArenaMember
胜利。比方说我们之前的例子如果只注册了onTap
事件而没有注册onLongPress
,那么state.members.length == 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
我们举个例子:
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'BuildContext Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MaterialApp(
home: Scaffold(
body: Center(
child: GestureDetector(
onTap: () {
print("onTap");
},
onLongPress: () {
print("onLongPress");
},
onDoubleTap: () {
print("onDoubleTap");
},
onVerticalDragDown: (details) {
print("onVerticalDragDown");
},
child: Container(
alignment: Alignment.center,
height: 500,
color: Colors.amber,
child: const Text("GestureDetector test"),
),
),
),
),
);
}
以上代码的执行结果我们非常清楚:
单击打印
onTap
长按打印
onLongPress
双击打印
onDoubleTap
竖向拖拽打印
onVerticalDragDown
以上我们可已知onTap
会分别和其他三个事件冲突。假设我们先只监听onTap
事件,看看GestureDetector
的核心源码实现:
onTap
@override
Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
final DeviceGestureSettings? gestureSettings = MediaQuery.maybeOf(context)?.gestureSettings;
if (onTapDown != null ||
onTapUp != null ||
onTap != null ||
onTapCancel != null ||
onSecondaryTap != null ||
onSecondaryTapDown != null ||
onSecondaryTapUp != null ||
onSecondaryTapCancel != null||
onTertiaryTapDown != null ||
onTertiaryTapUp != null ||
onTertiaryTapCancel != null
) {
gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(debugOwner: this),
(TapGestureRecognizer instance) {
instance
..onTapDown = onTapDown
..onTapUp = onTapUp
..onTap = onTap
..onTapCancel = onTapCancel
..onSecondaryTap = onSecondaryTap
..onSecondaryTapDown = onSecondaryTapDown
..onSecondaryTapUp = onSecondaryTapUp
..onSecondaryTapCancel = onSecondaryTapCancel
..onTertiaryTapDown = onTertiaryTapDown
..onTertiaryTapUp = onTertiaryTapUp
..onTertiaryTapCancel = onTertiaryTapCancel
..gestureSettings = gestureSettings;
},
);
}
//...
return RawGestureDetector(
gestures: gestures,
behavior: behavior,
excludeFromSemantics: excludeFromSemantics,
child: child,
);
}
创建一个TapGestureRecognizer
对象,并存到gestures
这个Map
中,TapGestureRecognizer
的继承关系如下:
class TapGestureRecognizer extends BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer extends OneSequenceGestureRecognizer extends GestureRecognizer extends GestureArenaMember
我们可以看到其实它就是一个GestureArenaMember
竞技场成员对象。然后返回一个RawGestureDetector
的Widget
。RawGestureDetector
是个StatefulWidget
,我们看看它对应的RawGestureDetectorState
中initState()
的实现:
@override
void initState() {
super.initState();
_semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
_syncAll(widget.gestures);
}
void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
assert(_recognizers != null);
final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!;
_recognizers = <Type, GestureRecognizer>{};
for (final Type type in gestures.keys) {
assert(gestures[type] != null);
assert(gestures[type]!._debugAssertTypeMatches(type));
assert(!_recognizers!.containsKey(type));
_recognizers![type] = oldRecognizers[type] ?? gestures[type]!.constructor();
assert(_recognizers![type].runtimeType == type, 'GestureRecognizerFactory of type $type created a GestureRecognizer of type ${_recognizers![type].runtimeType}. The GestureRecognizerFactory must be specialized with the type of the class that it returns from its constructor method.');
gestures[type]!.initializer(_recognizers![type]!);
}
for (final Type type in oldRecognizers.keys) {
if (!_recognizers!.containsKey(type)) {
oldRecognizers[type]!.dispose();
}
}
}
_syncAll()
方法的作用是将传入的gestures
给_recognizers
对象赋值。再看看build()
的实现:
@override
Widget build(BuildContext context) {
Widget result = Listener(
onPointerDown: _handlePointerDown,
onPointerPanZoomStart: _handlePointerPanZoomStart,
behavior: widget.behavior ?? _defaultBehavior,
child: widget.child,
);
if (!widget.excludeFromSemantics) {
result = _GestureSemantics(
behavior: widget.behavior ?? _defaultBehavior,
assignSemantics: _updateSemanticsForRenderObject,
child: result,
);
}
return result;
}
返回一个Listener
。这个Listener
我们在实践中也非常熟悉。监听并识别所有最底层的基础手势事件,但不做处理。它是个SingleChildRenderObjectWidget
,我们看看其createRenderObject
的实现:
@override
RenderPointerListener createRenderObject(BuildContext context) {
return RenderPointerListener(
onPointerDown: onPointerDown,
onPointerMove: onPointerMove,
onPointerUp: onPointerUp,
onPointerHover: onPointerHover,
onPointerCancel: onPointerCancel,
onPointerPanZoomStart: onPointerPanZoomStart,
onPointerPanZoomUpdate: onPointerPanZoomUpdate,
onPointerPanZoomEnd: onPointerPanZoomEnd,
onPointerSignal: onPointerSignal,
behavior: behavior,
);
}
在之前的章节我们提到过,在dispatchEvent()
时会遍历HitTestEntry
,执行entry.target.handleEvent()
方法,target
就是每个命中的RenderObject
对象。所以我们看看RenderPointerListener
里handleEvent()
的实现:
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
assert(debugHandleEvent(event, entry));
if (event is PointerDownEvent) {
return onPointerDown?.call(event);
}
if (event is PointerMoveEvent) {
return onPointerMove?.call(event);
}
if (event is PointerUpEvent) {
return onPointerUp?.call(event);
}
if (event is PointerHoverEvent) {
return onPointerHover?.call(event);
}
if (event is PointerCancelEvent) {
return onPointerCancel?.call(event);
}
if (event is PointerPanZoomStartEvent) {
return onPointerPanZoomStart?.call(event);
}
if (event is PointerPanZoomUpdateEvent) {
return onPointerPanZoomUpdate?.call(event);
}
if (event is PointerPanZoomEndEvent) {
return onPointerPanZoomEnd?.call(event);
}
if (event is PointerSignalEvent) {
return onPointerSignal?.call(event);
}
}
它非常的简单,就是将基础的手势事件回调回去。我们回到RawGestureDetectorState
的build()
:
@override
Widget build(BuildContext context) {
Widget result = Listener(
onPointerDown: _handlePointerDown,
onPointerPanZoomStart: _handlePointerPanZoomStart,
behavior: widget.behavior ?? _defaultBehavior,
child: widget.child,
);
if (!widget.excludeFromSemantics) {
result = _GestureSemantics(
behavior: widget.behavior ?? _defaultBehavior,
assignSemantics: _updateSemanticsForRenderObject,
child: result,
);
}
return result;
}
在我们的示例中其实只监听了onPointerDown
事件,其实现_handlePointerDown
代码如下:
void _handlePointerDown(PointerDownEvent event) {
assert(_recognizers != null);
for (final GestureRecognizer recognizer in _recognizers!.values) {
recognizer.addPointer(event);
}
}
遍历_recognizers
,执行recognizer.addPointer(event)
:
void addPointer(PointerDownEvent event) {
_pointerToKind[event.pointer] = event.kind;
if (isPointerAllowed(event)) {
addAllowedPointer(event);
} else {
handleNonAllowedPointer(event);
}
}
现在的场景中isPointerAllowed()
一定为true
,执行 addAllowedPointer(event)
:
@protected
void addAllowedPointer(PointerDownEvent event) { }
它是个抽象方法,我们看看子类BaseTapGestureRecognizer
的实现:
@override
void addAllowedPointer(PointerDownEvent event) {
assert(event != null);
if (state == GestureRecognizerState.ready) {
if (_down != null && _up != null) {
assert(_down!.pointer == _up!.pointer);
_reset();
}
_down = event;
}
if (_down != null) {
super.addAllowedPointer(event);
}
}
实际上调的就是super
的方法,即PrimaryPointerGestureRecognizer
的addAllowedPointer(event)
:
@override
void addAllowedPointer(PointerDownEvent event) {
super.addAllowedPointer(event);
if (state == GestureRecognizerState.ready) {
_state = GestureRecognizerState.possible;
_primaryPointer = event.pointer;
_initialPosition = OffsetPair(local: event.localPosition, global: event.position);
if (deadline != null) {
_timer = Timer(deadline!, () => didExceedDeadlineWithEvent(event));
}
}
}
继续调super
,即OneSequenceGestureRecognizer
的addAllowedPointer(event)
:
@override
@protected
void addAllowedPointer(PointerDownEvent event) {
startTrackingPointer(event.pointer, event.transform);
}
继续追踪startTrackingPointer()
的实现:
@protected
void startTrackingPointer(int pointer, [Matrix4? transform]) {
GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent, transform);
_trackedPointers.add(pointer);
assert(!_entries.containsValue(pointer));
_entries[pointer] = _addPointerToArena(pointer);
}
首先就是调用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()
的实现:
@override
void handleEvent(PointerEvent event) {
assert(state != GestureRecognizerState.ready);
if (state == GestureRecognizerState.possible && event.pointer == primaryPointer) {
final bool isPreAcceptSlopPastTolerance =
!_gestureAccepted &&
preAcceptSlopTolerance != null &&
_getGlobalDistance(event) > preAcceptSlopTolerance!;
final bool isPostAcceptSlopPastTolerance =
_gestureAccepted &&
postAcceptSlopTolerance != null &&
_getGlobalDistance(event) > postAcceptSlopTolerance!;
if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
resolve(GestureDisposition.rejected);
stopTrackingPointer(primaryPointer!);
} else {
handlePrimaryPointer(event);
}
}
stopTrackingIfPointerNoLongerDown(event);
}
这段代码实际上是先计算一下,如果接收到的是PointerMoveEvent
事件时,移动距离是不是足够小,如果是的话,即可以继续处理onTap
事件,会调用 handlePrimaryPointer(event)
:
@protected
void handlePrimaryPointer(PointerEvent event);
其实现在BaseTapGestureRecognizer
:
@override
void handlePrimaryPointer(PointerEvent event) {
if (event is PointerUpEvent) {
_up = event;
_checkUp();
} else if (event is PointerCancelEvent) {
resolve(GestureDisposition.rejected);
if (_sentTapDown) {
_checkCancel(event, '');
}
_reset();
} else if (event.buttons != _down!.buttons) {
resolve(GestureDisposition.rejected);
stopTrackingPointer(primaryPointer!);
}
}
由于目前我们还在处理PointerDownEvent
事件,所以不会做任何事情。到此为止,接收到PointerDownEvent
事件时,最重要的事情就是将TapGestureRecognizer
添加到手势竞技场等待。在只监听onTap
事件的时候,会判断接收到PointerMoveEvent
时的移动距离,如果足够小的话会继续等待。接下来,我们手势抬起,接收PointerUpEvent
,我们看看会发生什么。由于PrimaryPointerGestureRecognizer
的handleEvent()
对PointerUpEvent
并没有做什么,我们回到GestureBinding
的handleEvent()
:
@override // from HitTestTarget
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event);
if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
gestureArena.close(event.pointer);
} else if (event is PointerUpEvent || event is PointerPanZoomEndEvent) {
gestureArena.sweep(event.pointer);
} else if (event is PointerSignalEvent) {
pointerSignalResolver.resolve(event);
}
}
之前章节分析过,PointerUpEvent
是会走gestureArena.sweep(event.pointer)
清扫手势竞技场:
void sweep(int pointer) {
final _GestureArena? state = _arenas[pointer];
if (state == null) {
return; // This arena either never existed or has been resolved.
}
assert(!state.isOpen);
if (state.isHeld) {
state.hasPendingSweep = true;
assert(_debugLogDiagnostic(pointer, 'Delaying sweep', state));
return; // This arena is being held for a long-lived member.
}
assert(_debugLogDiagnostic(pointer, 'Sweeping', state));
_arenas.remove(pointer);
if (state.members.isNotEmpty) {
// First member wins.
assert(_debugLogDiagnostic(pointer, 'Winner: ${state.members.first}'));
state.members.first.acceptGesture(pointer);
// Give all the other members the bad news.
for (int i = 1; i < state.members.length; i++) {
state.members[i].rejectGesture(pointer);
}
}
}
由于当前竞技场中只有TapGestureRecognizer
这一个成员,会执行到 state.members.first.acceptGesture(pointer)
宣告胜利。acceptGesture()
的实现在BaseTapGestureRecognizer
中:
//BaseTapGestureRecognizer
@override
void acceptGesture(int pointer) {
super.acceptGesture(pointer);
if (pointer == primaryPointer) {
_checkDown();
_wonArenaForPrimaryPointer = true;
_checkUp();
}
}
//PrimaryPointerGestureRecognizer
@override
void acceptGesture(int pointer) {
if (pointer == primaryPointer) {
_stopTimer();
_gestureAccepted = true;
}
}
先调用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_null
super.postAcceptSlopTolerance = null,
@Deprecated(
'Migrate to supportedDevices. '
'This feature was deprecated after v2.3.0-1.0.pre.',
)
super.kind,
super.supportedDevices,
super.debugOwner,
}) : super(
deadline: duration ?? kLongPressTimeout,
);
const Duration kLongPressTimeout = Duration(milliseconds: 500);
super
里传给父类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
的实现:
@override
void addAllowedPointer(PointerDownEvent event) {
super.addAllowedPointer(event);
if (state == GestureRecognizerState.ready) {
_state = GestureRecognizerState.possible;
_primaryPointer = event.pointer;
_initialPosition = OffsetPair(local: event.localPosition, global: event.position);
if (deadline != null) {
_timer = Timer(deadline!, () => didExceedDeadlineWithEvent(event));
}
}
}
这里我们可以看到,如果deadline
不为空的话,启动一个500ms的Timer
,到时间后调用 didExceedDeadlineWithEvent(event)
方法:
@override
void didExceedDeadline() {
// Exceeding the deadline puts the gesture in the accepted state.
resolve(GestureDisposition.accepted);
_longPressAccepted = true;
super.acceptGesture(primaryPointer!);
_checkLongPressStart();
}
会在此时调用acceptGesture()
宣告LongPressGestureRecognizer
胜利,并回调onLongPress
给业务层进行处理。而不会等待PointerUpEvent
再去判定胜利者。给业务层的体验就是,长按时,不需手势抬起,就能收到onLongPress
回调。好了onLongPress
是怎么处理的我们也分析完了,我们继续分析示例中的onDoubleTap
双击事件的处理。
onDoubleTap
我们还是回到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()
方法里:
@override
void addAllowedPointer(PointerDownEvent event) {
if (_firstTap != null) {
if (!_firstTap!.isWithinGlobalTolerance(event, kDoubleTapSlop)) {
// Ignore out-of-bounds second taps.
return;
} else if (!_firstTap!.hasElapsedMinTime() || !_firstTap!.hasSameButton(event)) {
// Restart when the second tap is too close to the first (touch screens
// often detect touches intermittently), or when buttons mismatch.
_reset();
return _trackTap(event);
} else if (onDoubleTapDown != null) {
final TapDownDetails details = TapDownDetails(
globalPosition: event.position,
localPosition: event.localPosition,
kind: getKindForPointer(event.pointer),
);
invokeCallback<void>('onDoubleTapDown', () => onDoubleTapDown!(details));
}
}
_trackTap(event);
}
一开始_firstTap
一定是空,直接执行 _trackTap(event)
方法:
void _trackTap(PointerDownEvent event) {
_stopDoubleTapTimer();
final _TapTracker tracker = _TapTracker(
event: event,
entry: GestureBinding.instance.gestureArena.add(event.pointer, this),
doubleTapMinTime: kDoubleTapMinTime,
gestureSettings: gestureSettings,
);
_trackers[event.pointer] = tracker;
tracker.startTrackingPointer(_handleEvent, event.transform);
}
创建一个_TapTracker
对象,向手势竞技场注册自己然后调用tracker.startTrackingPointer(_handleEvent, event.transform)
。
void startTrackingPointer(PointerRoute route, Matrix4? transform) {
if (!_isTrackingPointer) {
_isTrackingPointer = true;
GestureBinding.instance.pointerRouter.addRoute(pointer, route, transform);
}
}
将route
添加到_routeMap
中,等待手势触发回调。我们继续按看看_handleEvent ()
的实现:
void _handleEvent(PointerEvent event) {
final _TapTracker tracker = _trackers[event.pointer]!;
if (event is PointerUpEvent) {
if (_firstTap == null) {
_registerFirstTap(tracker);
} else {
_registerSecondTap(tracker);
}
} else if (event is PointerMoveEvent) {
if (!tracker.isWithinGlobalTolerance(event, kDoubleTapTouchSlop)) {
_reject(tracker);
}
} else if (event is PointerCancelEvent) {
_reject(tracker);
}
}
主要关注PointerUpEvent
事件的接收,因为双击事件就是判断两次PointerUpEvent
事件是否符合双击的条件,我们先来看首次判断时_firstTap == null
,调用_registerFirstTap(tracker)
的情况:
void _registerFirstTap(_TapTracker tracker) {
_startDoubleTapTimer();
GestureBinding.instance.gestureArena.hold(tracker.pointer);
// Note, order is important below in order for the clear -> reject logic to
// work properly.
_freezeTracker(tracker);
_trackers.remove(tracker.pointer);
_clearTrackers();
_firstTap = tracker;
}
先调用_startDoubleTapTimer()
,启动双击的事件Timer
,默认是300ms。然后调用了GestureBinding.instance.gestureArena.hold(tracker.pointer)
:
void hold(int pointer) {
final _GestureArena? state = _arenas[pointer];
if (state == null) {
return; // This arena either never existed or has been resolved.
}
state.isHeld = true;
assert(_debugLogDiagnostic(pointer, 'Holding', state));
}
会将state.isHeld
设置为true
。然后我们回到GestureBinding
的handleEvent()
:
@override // from HitTestTarget
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event);
if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
gestureArena.close(event.pointer);
} else if (event is PointerUpEvent || event is PointerPanZoomEndEvent) {
gestureArena.sweep(event.pointer);
} else if (event is PointerSignalEvent) {
pointerSignalResolver.resolve(event);
}
}
在接收到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()
:
@override
void addAllowedPointer(PointerDownEvent event) {
if (_firstTap != null) {
if (!_firstTap!.isWithinGlobalTolerance(event, kDoubleTapSlop)) {
// Ignore out-of-bounds second taps.
return;
} else if (!_firstTap!.hasElapsedMinTime() || !_firstTap!.hasSameButton(event)) {
// Restart when the second tap is too close to the first (touch screens
// often detect touches intermittently), or when buttons mismatch.
_reset();
return _trackTap(event);
} else if (onDoubleTapDown != null) {
final TapDownDetails details = TapDownDetails(
globalPosition: event.position,
localPosition: event.localPosition,
kind: getKindForPointer(event.pointer),
);
invokeCallback<void>('onDoubleTapDown', () => onDoubleTapDown!(details));
}
}
_trackTap(event);
}
如果两次点击时间小于双击阈值的话,重新执行 _trackTap(event)
。我们回到DoubleTapGestureRecognizer
的_handleEvent ()
:
void _handleEvent(PointerEvent event) {
final _TapTracker tracker = _trackers[event.pointer]!;
if (event is PointerUpEvent) {
if (_firstTap == null) {
_registerFirstTap(tracker);
} else {
_registerSecondTap(tracker);
}
} else if (event is PointerMoveEvent) {
if (!tracker.isWithinGlobalTolerance(event, kDoubleTapTouchSlop)) {
_reject(tracker);
}
} else if (event is PointerCancelEvent) {
_reject(tracker);
}
}
_firstTap
不为空的情况,说明接收到第二个点击事件了,追踪_registerSecondTap()
的实现:
void _registerSecondTap(_TapTracker tracker) {
_firstTap!.entry.resolve(GestureDisposition.accepted);
tracker.entry.resolve(GestureDisposition.accepted);
_freezeTracker(tracker);
_trackers.remove(tracker.pointer);
_checkUp(tracker.initialButtons);
_reset();
}
两个手势事件都执行resolve(GestureDisposition.accepted)
方法,这个方法其实就是宣告胜利或失败的,我们看看它的实现:
void resolve(GestureDisposition disposition) {
_arena._resolve(_pointer, _member, disposition);
}
void _resolve(int pointer, GestureArenaMember member, GestureDisposition disposition) {
final _GestureArena? state = _arenas[pointer];
if (state == null) {
return; // This arena has already resolved.
}
assert(_debugLogDiagnostic(pointer, '${ disposition == GestureDisposition.accepted ? "Accepting" : "Rejecting" }: $member'));
assert(state.members.contains(member));
if (disposition == GestureDisposition.rejected) {
//...
} else {
assert(disposition == GestureDisposition.accepted);
if (state.isOpen) {
state.eagerWinner ??= member;
} else {
assert(_debugLogDiagnostic(pointer, 'Self-declared winner: $member'));
_resolveInFavorOf(pointer, state, member);
}
}
}
当传入GestureDisposition.accepted
时,会执行_resolveInFavorOf()
方法:
void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {
assert(state == _arenas[pointer]);
assert(state != null);
assert(state.eagerWinner == null || state.eagerWinner == member);
assert(!state.isOpen);
_arenas.remove(pointer);
for (final GestureArenaMember rejectedMember in state.members) {
if (rejectedMember != member) {
rejectedMember.rejectGesture(pointer);
}
}
member.acceptGesture(pointer);
}
在sweep()
之前,直接宣告胜利。到此为止,onDoubleTap
的流程也分析完成了。之前的示例中,我们还剩下一个onVerticalDragDown
没有分析。下面我们再分析一下onVerticalDragDown
。
onVerticalDragDown
重新回到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
实现的:
@override
void handleEvent(PointerEvent event) {
//...
if (event is PointerMoveEvent || event is PointerPanZoomUpdateEvent) {
final Offset delta = (event is PointerMoveEvent) ? event.delta : (event as PointerPanZoomUpdateEvent).panDelta;
final Offset localDelta = (event is PointerMoveEvent) ? event.localDelta : (event as PointerPanZoomUpdateEvent).localPanDelta;
final Offset position = (event is PointerMoveEvent) ? event.position : (event.position + (event as PointerPanZoomUpdateEvent).pan);
final Offset localPosition = (event is PointerMoveEvent) ? event.localPosition : (event.localPosition + (event as PointerPanZoomUpdateEvent).localPan);
if (_state == _DragState.accepted) {
_checkUpdate(
sourceTimeStamp: event.timeStamp,
delta: _getDeltaForDetails(localDelta),
primaryDelta: _getPrimaryValueFromOffset(localDelta),
globalPosition: position,
localPosition: localPosition,
);
} else {
_pendingDragOffset += OffsetPair(local: localDelta, global: delta);
_lastPendingEventTimestamp = event.timeStamp;
_lastTransform = event.transform;
final Offset movedLocally = _getDeltaForDetails(localDelta);
final Matrix4? localToGlobalTransform = event.transform == null ? null : Matrix4.tryInvert(event.transform!);
_globalDistanceMoved += PointerEvent.transformDeltaViaPositions(
transform: localToGlobalTransform,
untransformedDelta: movedLocally,
untransformedEndPosition: localPosition
).distance * (_getPrimaryValueFromOffset(movedLocally) ?? 1).sign;
if (_hasSufficientGlobalDistanceToAccept(event.kind, gestureSettings?.touchSlop)) {
resolve(GestureDisposition.accepted);
}
}
}
if (event is PointerUpEvent || event is PointerCancelEvent || event is PointerPanZoomEndEvent) {
_giveUpPointer(event.pointer);
}
}
我们截取了部分重要的代码,在PointerMoveEvent
阶段,会一直计算_globalDistanceMoved
的值,_hasSufficientGlobalDistanceToAccept()
用来判断移动的距离是否大于固定的阈值:
@override
bool _hasSufficientGlobalDistanceToAccept(PointerDeviceKind pointerDeviceKind, double? deviceTouchSlop) {
return _globalDistanceMoved.abs() > computeHitSlop(pointerDeviceKind, gestureSettings);
}
如果是的话,执行resolve(GestureDisposition.accepted)
,即在move
阶段宣告胜利。
手势事件拦截
我们在开发过程中有时候需要对手势事件进行拦截,这个是怎么实现的呢?我们回想一下第三章讲解的hitTest()
,优先对子进行命中测试,如果命中即由子来实现手势事件。这么看来拦截的思路就是不要让子进行hitTest()
。Flutter
中的AbsorbPointer
就是来干这件事情的。我们来看一下它的关键处理:
@override
bool hitTest(BoxHitTestResult result, { required Offset position }) {
return absorbing
? size.contains(position)
: super.hitTest(result, position: position);
}
重写hitTest()
,如果absorbing
为true
,那么直接判断手势事件的position
是否已经在AbsorbPointer
的范围内,不会再执行super.hitTest(result, position: position)
对子来判定hitTest()
了。
总结
先上一张图,简单的说明一下Flutter
处理手势事件的流程:
Flutter
对于手势事件的处理流程的思路其实非常直观,在down
事件时通过hitTest()
确定可能触发手势事件的Widget
并给它们设置优先级,再调用dispatchEvent
对手势事件进行分发并向竞技场注册成员等待判定。在handleEvent()
时会根据条件解决冲突判定手势的胜利者。本文通过源码分析对Flutter
的手势事件分发与冲突处理进行了说明,同时通过GestureDetector
的示例分析了不同冲突的具体处理方式,以及如何对手势事件进行拦截,希望对大家理解Flutter
的手势事件有所帮助。