持续创作,加速成长!这是我参与「日新计划 6 月更文挑战」的第1天,点击查看活动详情
前面我们介绍了 runApp
、BaseBinding
,知道了 Flutter
的初始化就是一堆 Binding
的初始化和服务注册,GestureBinding
是第一个初始化的 Binding
,见名知意,这个 Binding
是专门用来处理手势的,这一篇就看看 Flutter
是怎么处理手势的。
往期精彩:
Flutter 必知必会系列 —— runApp 做了啥
Flutter 必知必会系列 —— mixin 和 BindingBase 的巧妙配合
Flutter 的手势从哪里来
Flutter
仅仅是一个 UI 框架,是没有从屏幕读取手势功能的,它只能被动的响应。我们以 Android 为例,来找一下手势是怎么传递的。
首先看 Flutter
侧的手势起点。
/// GestureBinding
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
@override
void initInstances() {
super.initInstances();
_instance = this;
window.onPointerDataPacket = _handlePointerDataPacket;
}
}
GestureBinding
仅仅是给 onPointerDataPacket
赋值,所以我们看赋值的动作干了啥。
class SingletonFlutterWindow extends FlutterWindow {
PointerDataPacketCallback? get onPointerDataPacket => platformDispatcher.onPointerDataPacket;
set onPointerDataPacket(PointerDataPacketCallback? callback) {
platformDispatcher.onPointerDataPacket = callback; //第一处
}
}
我们看第一处的代码,仅仅是给 platformDispatcher
赋值,我们继续追。
platformDispatcher
是 PlatformDispatcher 的实例,并且 PlatformDispatcher 是单例的,所以说一个 Native 的 FlutterView
对应一个 Flutter 的 PlatformDispatcher
实例。(这里大家需要理解一点,Native 的 FlutterView 对应的就是 Flutter 程序,容器是承载 FlutterView 的 Activity 或者 Fragment)
见名知意,这个类就是 Native 消息的分发器。我们看赋值代码。
/// PlatformDispatcher
class PlatformDispatcher {
PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket;
PointerDataPacketCallback? _onPointerDataPacket;
Zone _onPointerDataPacketZone = Zone.root;
set onPointerDataPacket(PointerDataPacketCallback? callback) {
_onPointerDataPacket = callback;
_onPointerDataPacketZone = Zone.current;
}
}
所以,GestureBinding
中的 window
赋值语句,其实是给 PlatformDispatcher
的 _onPointerDataPacket
赋值,那么调用 _onPointerDataPacket
的地方,就是发起 Flutter 手势的地方。
class PlatformDispatcher {
// Called from the engine, via hooks.dart
void _dispatchPointerDataPacket(ByteData packet) {
if (onPointerDataPacket != null) {
_invoke1<PointerDataPacket>(
onPointerDataPacket,
_onPointerDataPacketZone,
_unpackPointerDataPacket(packet),
);
}
}
}
就是 PlatformDispatcher 的 _dispatchPointerDataPacket
方法,而调用 _dispatchPointerDataPacket
的是
我们看到调用的方法是被 @pragma(‘vm:entry-point’) 注解的,也就是从 Native 调用的,而非 Flutter 层。
所以,就是我们前面得到的结论: Flutter 层只是被动的响应来自 Native 的调用,调用的入口是 _dispatchPointerDataPacket。
既然是 Native 层,那就是 FlutterView 了,大概率是重写 onTouch
方法。我们看 Native 的处理逻辑,既然承载 Flutter
的是 FlutterView
,我们看其手势处理方法。
/// FlutterView
public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseCursorViewDelegate {
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
if (!isAttachedToFlutterEngine()) {
return super.onTouchEvent(event);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
requestUnbufferedDispatch(event);
}
return androidTouchProcessor.onTouchEvent(event);
}
}
基本就是讲手势透传给了 androidTouchProcessor
, androidTouchProcessor 是谁呢?AndroidTouchProcessor
描述如下:
Sends touch information from Android to Flutter in a format that Flutter understands.
把手势的信息从 Android 层传给 Flutter,关键是对信息进行了格式转换。
我们看它的逻辑:
/// AndroidTouchProcessor
public class AndroidTouchProcessor {
public boolean onTouchEvent(@NonNull MotionEvent event) {
return onTouchEvent(event, IDENTITY_TRANSFORM);
}
public boolean onTouchEvent(@NonNull MotionEvent event, Matrix transformMatrix) {
int pointerCount = event.getPointerCount();
// 准备数据包
ByteBuffer packet =
ByteBuffer.allocateDirect(pointerCount * POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD);
packet.order(ByteOrder.LITTLE_ENDIAN);
int maskedAction = event.getActionMasked();
int pointerChange = getPointerChangeForAction(event.getActionMasked());
if (updateForSinglePointer) {
//添加数据包 DOWN 事件
addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet);
} else if (updateForMultiplePointers) {
//添加数据包 UP 事件
addPointerForIndex(event, event.getActionIndex(), pointerChange, 0, transformMatrix, packet);
} else {
for (int p = 0; p < pointerCount; p++) {
// 添加数据包 MOVE 事件
addPointerForIndex(event, p, pointerChange, 0, transformMatrix, packet);
}
}
// 数据包发送给 Flutter
renderer.dispatchPointerDataPacket(packet, packet.position());
return true;
}
}
onTouchEvent
的处理:
准备数据包,将数据封装成 Flutter
认识的格式,添加到数据包中,renderer
发送给 Flutter。
我们先看封装的格式,在 addPointerForIndex
中:
//代码片段
packet.putLong(motionEventId); // motionEventId
packet.putLong(timeStamp); // time_stamp
packet.putLong(pointerChange); // change
packet.putLong(pointerKind); // kind
packet.putLong(signalKind); // signal_kind
packet.putLong(event.getPointerId(pointerIndex)); // device
packet.putLong(0);
if (signalKind == PointerSignalKind.SCROLL) {
packet.putDouble(-event.getAxisValue(MotionEvent.AXIS_HSCROLL)); // scroll_delta_x
packet.putDouble(-event.getAxisValue(MotionEvent.AXIS_VSCROLL)); // scroll_delta_y
} else {
packet.putDouble(0.0); // scroll_delta_x
packet.putDouble(0.0); // scroll_delta_x
}
这一段代码是不是和 Flutter
侧的代码很像呀:
我们看 render : FlutterRenderer 是怎么把数据包发送过去的。
/// FlutterRenderer
public void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position) {
flutterJNI.dispatchPointerDataPacket(buffer, position);
}
/// FlutterJNI
@UiThread
public void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position) {
ensureRunningOnMainThread();
ensureAttachedToNative();
nativeDispatchPointerDataPacket(nativeShellHolderId, buffer, position);
}
private native void nativeDispatchPointerDataPacket(
long nativeShellHolderId, @NonNull ByteBuffer buffer, int position);
调用的就是 JNI
的代码,JNI
调用到了 Engine
底层的代码,尽而调用到了 Flutter
层,如下图中 engine
的代码。
下个系列详细介绍 C++ 层是怎么和 Flutter 交互的,这个系列暂且只需要知道 Flutter 层设置了一个手势处理方法 PlatformDispatcher
的 _dispatchPointerDataPacket
,当识别到手势交互时,Android 的 FlutterView 对原始的手势进行了格式的封装,然后通过 JNI
调用到了上面的 _dispatchPointerDataPacket
即可。
现在知道了手势怎么来的,下面我们来看怎么处理手势的。
Flutter 是怎么处理手势的
上面讲的都是 Flutter
和 Native
交互的东西,对于我们使用者来说,我们的起点就是 GestureBinding
的 _handlePointerDataPacket
方法,只要知道它的逻辑,就知道 Flutter 的手势逻辑了。
// GestureBinding
void _handlePointerDataPacket(ui.PointerDataPacket packet) {
_pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio)); //第一处
if (!locked)
_flushPointerEventQueue();
}
void _flushPointerEventQueue() {
while (_pendingPointerEvents.isNotEmpty)
handlePointerEvent(_pendingPointerEvents.removeFirst()); //第二处
}
Native 调用 Flutter 的时候是一个数据包,所以需要将数据包进行转换,转换的结果就是 _pendingPointerEvents : Queue 队列,然后 flush 队列。
flush
就是从前往后(先进先出)的边移除边处理每一个事件。
比如:队列中有三个事件:A、B、C。
那么:会依次调用 handlePointerEvent(A)、handlePointerEvent(B)、handlePointerEvent(C)。
那我们继续看 handlePointerEvent
是怎么处理的。
/// GestureBinding
void handlePointerEvent(PointerEvent event) {
///... 省略代码
_handlePointerEventImmediately(event);
}
void _handlePointerEventImmediately(PointerEvent event) {
HitTestResult? hitTestResult;
if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent) {
hitTestResult = HitTestResult();
hitTest(hitTestResult, event.position);
if (event is PointerDownEvent) {
_hitTests[event.pointer] = hitTestResult;
}// 第一处
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
hitTestResult = _hitTests.remove(event.pointer); //第二处
} else if (event.down) {
hitTestResult = _hitTests[event.pointer];
}
if (hitTestResult != null ||
event is PointerAddedEvent ||
event is PointerRemovedEvent) {
dispatchEvent(event, hitTestResult); //第三处
}
}
handlePointerEvent
就是直接调用了 _handlePointerEventImmediately
方法,在方法内部进行了事件的处理。和我们预想的是不是很像,根据事件类型处理不同的事情。
一般情况下: Down 事件用来记录,Up 事件用来结束,其他的事件做分发。就是对应了第一处、第二处和第三处。
我们先看 PointerDownEvent 事件是怎么记录事件开始的。
Down 事件记录开始
/// GestureBinding
void _handlePointerEventImmediately(PointerEvent event) {
HitTestResult? hitTestResult;
if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent) {
hitTestResult = HitTestResult(); //第一处
hitTest(hitTestResult, event.position); //第二处
if (event is PointerDownEvent) {
_hitTests[event.pointer] = hitTestResult;
}
}
/// 代码省略...
if (hitTestResult != null ||
event is PointerAddedEvent ||
event is PointerRemovedEvent) {
dispatchEvent(event, hitTestResult); //第三处
}
}
就做了下面件事:
-
① 声明
HitTestResult
,我们可以认为HitTestResult
是一个集合,集合中存放的就是可以接受处理这个落点的所有对象。 -
② 发起点击检测,就是看落点是不是在某个
RenderObject
上 -
③
_hitTests
的Map
中添加第一处声明的HitTestResult
,Map 的Key
是手势的 id,_hitTests
是 GestureBinding 的成员变量,所以每一个事件序列都会以 Map 的形式保存下来。
下面我们详细看。
第一处是声明了 hitTestResult
,并在第二处直接调用了 hitTest
。
注意看,hitTest 是有被重写的表示的,重写的地方就是 RenderBinding
,方法如下:
/// RenderBinding
@override
void hitTest(HitTestResult result, Offset position) {
renderView.hitTest(result, position: position);
super.hitTest(result, position);
}
注意看,调用了根 RenderView
的点击检测,然后再调用上面 GestureBinding
的点击检测。
根 RenderView
的点击检测做了啥呢?
/// RenderView
bool hitTest(HitTestResult result, { required Offset position }) {
if (child != null)
child!.hitTest(BoxHitTestResult.wrap(result), position: position);
result.add(HitTestEntry(this));
return true;
}
/// RenderObject
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;
}
就做了两件事:
-
① 发起子节点的落点检测,这个过程是一个递归的过程,会将所有宽高包含的子孙节点都添加到集合中
-
② 将
根View
添加到集合中
这就是 根View
的落点检测过程,现在回过头看:
/// RenderBinding
@override
void hitTest(HitTestResult result, Offset position) {
renderView.hitTest(result, position: position);
super.hitTest(result, position);
}
renderView.hitTest
就是添加了所有的符合条件的 RenderObject
,super
是什么呢?super
是 GestureBinding
的 hitTest,如果不熟悉可以看前面的 Flutter 必知必会系列 —— mixin 和 BindingBase 的巧妙配合,如下:
把 GestureBinding 本身添加到了集合中。我们举个例子:
落点落在了 B 上,B 是 A 的子节点,那么形成的 hitTestResult 中会维护上图中黑色的列表,最巧妙的地方是 HitTestResult 维护的列表中最后一个元素是 Binding。这样就形成了:Binding 是手势的起点和终点,至于手势上的有哪些节点,它并不关心,它只负责处理框架层的东西。
那么框架层的东西是啥呢?
就是 down 事件之后的 dispatchEvent
。
dispatchEvent 分发处理事件
/// GestureBinding
void _handlePointerEventImmediately(PointerEvent event) {
HitTestResult? hitTestResult;
if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent) {
hitTestResult = HitTestResult();
hitTest(hitTestResult, event.position);
if (event is PointerDownEvent) {
_hitTests[event.pointer] = hitTestResult;
}
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
hitTestResult = _hitTests.remove(event.pointer);
} else if (event.down) {
hitTestResult = _hitTests[event.pointer];
}
if (hitTestResult != null ||
event is PointerAddedEvent ||
event is PointerRemovedEvent) {
dispatchEvent(event, hitTestResult); //第一处
}
}
我们再看 _handlePointerEventImmediately
方法,在判断完具体的事件类型之后,就走到了第一处代码处。
见名知意,这个就是分发。分发就是将事件分给应该处理的节点,比如上图中的 A 和 B 等等。
/// GestureBinding
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
///... 代码省略
for (final HitTestEntry entry in hitTestResult.path) {
try {
entry.target.handleEvent(event.transformed(entry.transform), entry);
} catch (exception, stack) {
}
}
}
注意一点: 在循环遍历中,最后一个元素是 Binding
,其他的元素都是RenderObject
,我们暂且不看,只看框架层的东西。
/// GestureBinding
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event); //第一处
if (event is PointerDownEvent) { //第二处
gestureArena.close(event.pointer);
} else if (event is PointerUpEvent) {
gestureArena.sweep(event.pointer);
} else if (event is PointerSignalEvent) {
pointerSignalResolver.resolve(event);
}
}
第一处就是发起手势的处理,pointerRouter
是路由器的意思,route
方法 就是路由到具体的处理。
第二处就是针对不同的事件框架应该干啥事,这里涉及到了手势竞技场的概念。
我们具体来看,先看路由的处理。
///PointerRouter
void route(PointerEvent event) {
final Map<PointerRoute, Matrix4?>? routes = _routeMap[event.pointer];
final Map<PointerRoute, Matrix4?> copiedGlobalRoutes = Map<PointerRoute, Matrix4?>.from(_globalRoutes);
if (routes != null) {
_dispatchEventToRoutes(
event,
routes,
Map<PointerRoute, Matrix4?>.from(routes),
);
}
_dispatchEventToRoutes(event, _globalRoutes, copiedGlobalRoutes);
}
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);
}
});
}
@pragma('vm:notify-debugger-on-exception')
void _dispatch(PointerEvent event, PointerRoute route, Matrix4? transform) {
try {
event = event.transformed(transform);
route(event); //第一处
} catch (exception, stack) {
}
}
_routeMap
是一个 Map ,Map 的 key 就是事件序列的 id ,value 是 PointerRoute
是事件的处理回调。
_dispatchEventToRoutes
就是分发,具体走到了 _dispatch
,然后走到了第一处的 route(event)
。
这里注意一点: PointerRoute
是一个函数签名,就是 _routeMap
中的 value,也就是说 谁注册了,它就路由到谁。
那么谁注册了呢?
就是调用 addRoute
的地方,具体就是手势收到落点的地方。
/// GestureRecognizer
void startTrackingPointer(int pointer, [Matrix4? transform]) {
GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform); //第一处
_trackedPointers.add(pointer);
_entries[pointer] = _addPointerToArena(pointer);
}
在手势需要追踪的时候,就调用到了 第一处的代码,第一处就是具体的手势注册代码,注册的 PointerRoute
就是手势的 handleEvent
。
所以流程就是: 具体的手势注册自己的事件处理方法,然后框架层负责调用。
我们再看手势竞技场干了啥。
/// GestureRecognizer
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event);
if (event is PointerDownEvent) {
gestureArena.close(event.pointer);
} else if (event is PointerUpEvent) {
gestureArena.sweep(event.pointer);
} else if (event is PointerSignalEvent) {
pointerSignalResolver.resolve(event);
}
}
如果是 PointerDownEvent
事件,那么 gestureArena
就关闭掉,关闭的意思是:
-
如果竞技场中只有一个元素,那么该元素就获胜,获得获得手势处理权,就开始处理手势。
-
如果竞技场中没有元素,那么没有元素获胜。
-
如果竞技场中有特别渴望胜利的,那么特别渴望胜利的就获得手势处理权,比如 EagerGestureRecognizer,这个机会很少,因为会打破默认的处理。
如果是 PointerUpEvent
事件,那么就需要竞争了 sweep
。竞争的结果就是第一个获得手势处理权,其他的手势拒绝手势。
上面就是框架层的手势处理,下面我们看看具体的手势组件。
手势组件源码解读
手势处理组件我们常用的就是 Listener
、GestureDetector
等等。GestureDetector
包裹的就是 Listener
,下面我们看看它俩的源码。
Listener 源码
class Listener extends SingleChildRenderObjectWidget {
const Listener({
Key? key,
this.onPointerDown,
this.onPointerMove,
this.onPointerUp,
this.onPointerHover,
this.onPointerCancel,
this.onPointerSignal,
this.behavior = HitTestBehavior.deferToChild,
Widget? child,
}) : assert(behavior != null),
super(key: key, child: child);
@override
RenderPointerListener createRenderObject(BuildContext context) {
return RenderPointerListener(
onPointerDown: onPointerDown,
onPointerMove: onPointerMove,
onPointerUp: onPointerUp,
onPointerHover: onPointerHover,
onPointerCancel: onPointerCancel,
onPointerSignal: onPointerSignal,
behavior: behavior,
);
}
}
Listener
是 SingleChildRenderObjectWidget
类型的组件, 构造方法中的 onPointerDown
中的就是 Down
事件的回调。具体的逻辑在 RenderPointerListener
中。
/// RenderPointerListener
class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
@override
void handleEvent(PointerEvent event, HitTestEntry 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 PointerSignalEvent)
return onPointerSignal?.call(event);
}
}
我们看到了 handleEvent
方法,针对每一种类型处理了不同的回调。
还记得 handleEvent
是谁调用的吗? handleEvent
是从 RenderObject
继承的方法。
// GestureBinding
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
for (final HitTestEntry entry in hitTestResult.path) {
try {
entry.target.handleEvent(event.transformed(entry.transform), entry);
} catch (exception, stack) {
}
}
}
就是这里的 handleEvent
方法,hitTestResult
的 path
就是我们的RenderObject
,也就是 RenderPointerListener
,所以 entry.target.handleEvent
会走到 handleEvent
中。
注意一点: 如果我们代码只是单纯的使用了 Listener ,并不会走到框架的手势处理,只会走到 entry.target.handleEvent 就结束了。
下面我们看手势的组件处理。
GestureDetector 源码
GestureDetector
是 StatelessWidget
,为每一种手势都封装了一个回调,比如点击的就是 onTapDown
,其逻辑都在 build 方法中。
/// GestureDetector
@override
Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
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;
},
);
}
/// 代码省略
return RawGestureDetector(
gestures: gestures,
behavior: behavior,
excludeFromSemantics: excludeFromSemantics,
child: child,
);
}
逻辑比较简单:根据我们设置的回调生成手势,比如 onTapDown 就是 TapGestureRecognizer 手势,然后将手势封装到了 gestures
中,并在最后返回了 RawGestureDetector
组件。
RawGestureDetector
是 StatefulWidget 类型的组件,其逻辑在 RawGestureDetectorState
的 initState 和 build 方法中。
/// RawGestureDetectorState
void initState() {
super.initState();
_semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
_syncAll(widget.gestures);
}
void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!;
_recognizers = <Type, GestureRecognizer>{};
for (final Type type in gestures.keys) {
_recognizers![type] = oldRecognizers[type] ?? gestures[type]!.constructor();
gestures[type]!.initializer(_recognizers![type]!); //第一处
}
}
初始化的过程就是 gestures
中手势初始化的过程,这个初始化就是手势中回调的赋值。比如 TapGestureRecognizer 的 onTapDown 赋值为我们开发者设置的自定义回调,重点是第一处的代码。这里用到了工厂模式大家可以细细品味一下。
build 的逻辑如下:
/// RawGestureDetectorState
@override
Widget build(BuildContext context) {
Widget result = Listener(
onPointerDown: _handlePointerDown,
behavior: widget.behavior ?? _defaultBehavior,
child: widget.child,
);
return result;
}
我们看,就是返回了 Listener
组件,然后给 Listener
的 Down
事件赋值为 _handlePointerDown
回调,我们知道 Listener
是不处理手势的,只负责接受 Down
等事件的回调,所以 Down 事件的时候,会调用到_handlePointerDown
方法中。
/// RawGestureDetectorState
void _handlePointerDown(PointerDownEvent event) {
for (final GestureRecognizer recognizer in _recognizers!.values)
recognizer.addPointer(event);
}
我们看代码比较简单,就是将手势注册到了路由中。具体如下:
/// GestureRecognizer
void addPointer(PointerDownEvent event) {
_pointerToKind[event.pointer] = event.kind;
if (isPointerAllowed(event)) {
addAllowedPointer(event);
} else {
handleNonAllowedPointer(event);
}
}
@override
@protected
void addAllowedPointer(PointerDownEvent event) {
startTrackingPointer(event.pointer, event.transform);
}
@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);
}
我们看最终调用到了手势的 startTrackingPointer
中,我们看第一处的代码,第一处就是我们前面介绍的注册 handleEvent 的地方。
那么 handleEvent
是啥呢?
/// PrimaryPointerGestureRecognizer
@override
void handleEvent(PointerEvent event) {
if (state == GestureRecognizerState.possible && event.pointer == primaryPointer) {
/// 代码省略
if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
resolve(GestureDisposition.rejected);
stopTrackingPointer(primaryPointer!);
} else {
handlePrimaryPointer(event); //第一处
}
}
stopTrackingIfPointerNoLongerDown(event);
}
我们看第一处代码,第一处代码就是手势接收到 Down
事件的处理。
/// PrimaryPointerGestureRecognizer
@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!);
}
}
上面方法中,我们发现没有关于 Down
事件的处理,所以 HitTestResult
的 path 中走到了 Binding 的处理。
/// GestureBinding
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event);
if (event is PointerDownEvent) {
gestureArena.close(event.pointer);
} else if (event is PointerUpEvent) {
gestureArena.sweep(event.pointer);
} else if (event is PointerSignalEvent) {
pointerSignalResolver.resolve(event);
}
}
也就是 gestureArena.close(event.pointer) 的代码,根据我们上面的介绍,如果我们只设置一个 onTapDown 的话,直接关闭竞技场:
void _tryToResolveArena(int pointer, _GestureArena state) {
if (state.members.length == 1) {
scheduleMicrotask(() => _resolveByDefault(pointer, state)); //第一处
} else if (state.members.isEmpty) {
_arenas.remove(pointer);
} else if (state.eagerWinner != null) {
_resolveInFavorOf(pointer, state, state.eagerWinner!);
}
}
void _resolveByDefault(int pointer, _GestureArena state) {
if (!_arenas.containsKey(pointer))
return; // Already resolved earlier.
final List<GestureArenaMember> members = state.members;
_arenas.remove(pointer);
state.members.first.acceptGesture(pointer); //第二处
}
也就是第一处代码的地方。点击手势直接获胜。走到第二处代码,也就是手势的 acceptGesture 方法进行调用。
@override
void acceptGesture(int pointer) {
super.acceptGesture(pointer);
if (pointer == primaryPointer) {
_checkDown();
_wonArenaForPrimaryPointer = true;
_checkUp();
}
}
void _checkDown() {
if (_sentTapDown) {
return;
}
handleTapDown(down: _down!);
_sentTapDown = true;
}
@protected
@override
void handleTapDown({required PointerDownEvent down}) {
final TapDownDetails details = TapDownDetails(
globalPosition: down.position,
localPosition: down.localPosition,
kind: getKindForPointer(down.pointer),
);
switch (down.buttons) {
case kPrimaryButton:
if (onTapDown != null)
invokeCallback<void>('onTapDown', () => onTapDown!(details)); //第一处
break;
case kSecondaryButton:
if (onSecondaryTapDown != null)
invokeCallback<void>('onSecondaryTapDown', () => onSecondaryTapDown!(details));
break;
case kTertiaryButton:
if (onTertiaryTapDown != null)
invokeCallback<void>('onTertiaryTapDown', () => onTertiaryTapDown!(details));
break;
default:
}
}
最终会调用到第一处代码,也就是调用到了我们的自定义的 onTapDown 回调中。
上面就是手势组件的处理过程。
总结
上面我们就从宏观上介绍了手势的处理过程,从 Native 中接受到事件的数据序列,GestureBinding 接受事件序列,然后进行格式转换,从框架层进行处理。如果没有手势参与的话,就直接调用了具体的 handle 事件。如果有手势参与的话,那么就通过手势竞技场来调用注册好的手势回调。
更加细节的手势处理,可以在此基础上进行深究,比如多个手势怎么具体的进行获胜判断的等等。