Flutter 事件处理源码剖析
文章目录
博主相关文章列表
Flutter 框架实现原理
Flutter 框架层启动源码剖析
Flutter 页面更新流程剖析
Flutter 事件处理源码剖析
Flutter 路由源码剖析
Flutter 安卓平台源码剖析(一)
Flutter 自定义控件之RenderObject
Flutter 事件处理源码剖析
事件都是由硬件收集起来的,然后传递给系统处理。在Flutter中,则是由平台层传递给Flutter引擎,再由引擎通知给上层应用处理。在ui.Window
类中,通过onPointerDataPacket
回调来通知上层。
事件的分发
结合前面的启动流程分析,我们知道Flutter框架层的事件源头在GestureBinding
中,找到initInstances
方法实现
/// [GestureBinding]
void initInstances() {
super.initInstances();
_instance = this;
window.onPointerDataPacket = _handlePointerDataPacket;
}
void _handlePointerDataPacket(ui.PointerDataPacket packet) {
// 将指针数据转换为逻辑像素,这样就可以以独立于设备的方式定义触摸斜率
_pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
if (!locked)
_flushPointerEventQueue();
}
void _flushPointerEventQueue() {
assert(!locked);
while (_pendingPointerEvents.isNotEmpty)
_handlePointerEvent(_pendingPointerEvents.removeFirst());
}
void _handlePointerEvent(PointerEvent event) {
HitTestResult hitTestResult;
if (event is PointerDownEvent || event is PointerSignalEvent) {
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) {
// 因为在指针down 时发生的事件(如PointerMoveEvents)应该被派发到与其初始PointerDownEvent相同的地方,
// 我们希望重新使用指针down时找到的路径,而不是每次都进行命中检测。
hitTestResult = _hitTests[event.pointer];
}
if (hitTestResult != null ||
event is PointerHoverEvent ||
event is PointerAddedEvent ||
event is PointerRemovedEvent) {
// 事件分发
dispatchEvent(event, hitTestResult);
}
}
在GestureBinding
中的hitTest
实现仅仅将自己添加到HitTestResult
的List
中,由此可见此处并不是真正的逻辑
void hitTest(HitTestResult result, Offset position) {
result.add(HitTestEntry(this));
}
在前面启动源码剖析时,我们知道WidgetsFlutterBinding
混入了多个Binding
,相当于多继承,而此处最先调用的是RendererBinding
中的hitTest
方法
flutter\lib\src\rendering\binding.dart
/// [RendererBinding]
void hitTest(HitTestResult result, Offset position) {
assert(renderView != null);
renderView.hitTest(result, position: position);
// 最后调用GestureBinding的hitTest,将自己添加到列表的最末
super.hitTest(result, position);
}
flutter\lib\src\rendering\view.dart
/// [RenderView]
bool hitTest(HitTestResult result, { Offset position }) {
if (child != null)
// 尝试将符合条件的 child 控件添加到 HitTestResult 里
child.hitTest(BoxHitTestResult.wrap(result), position: position);
// 添加自身
result.add(HitTestEntry(this));
return true;
}
flutter\lib\src\rendering\box.dart
/// [RenderBox]
bool hitTest(BoxHitTestResult result, { @required Offset position }) {
// 判断自身是否处于响应区域之内
if (_size.contains(position)) {
// 尝试添加下级的 child 和自己。递归调用,从控件树自下而上的得到了一个相应控件列表
// 控件树最下面的叶子节点则会处于列表的first位置
if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
result.add(BoxHitTestEntry(this, position));
return true;
}
}
return false;
}
通过RendererBinding
中的super.hitTest(result, position)
调用,进入GestureBinding
中的hitTest
方法,将自己添加到列表的最末,接着通过dispatchEvent
进行事件的分发处理。
flutter\lib\src\gestures\binding.dart
/// [GestureBinding]
void dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {
// 没有命中测试信息,则通过 pointerRouter.route 将事件分发到全局处理
if (hitTestResult == null) {
assert(event is PointerHoverEvent || 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) {
// 省略......
}
}
}
HitTestTarget
有两个子类,GestureBinding
和RenderObject
。这里的HitTestEntry.target
对象主要是一个RenderObject
,因此调用RenderObject
的handleEvent
方法。我们知道,普通控件并不能响应触摸事件,只有几个特定的Widget可以专门处理事件,例如GestureDetector
,同样的,RenderObject
及其子类也并没有都实现handleEvent
方法,其中主要处理事件的RenderObject
子类是RenderPointerListener
。
另外,不要忘记hitTestResult
的列表中最后添加的是GestureBinding
自己,for
循环遍历到最后调用的是GestureBinding.handleEvent
方法
flutter\lib\src\rendering\proxy_box.dart
/// [RenderPointerListener]
RenderPointerListener({
this.onPointerDown,
this.onPointerMove,
this.onPointerUp,
this.onPointerCancel,
this.onPointerSignal,
HitTestBehavior behavior = HitTestBehavior.deferToChild,
RenderBox child,
}) : super(behavior: behavior, child: child);
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
assert(debugHandleEvent(event, entry));
if (onPointerDown != null && event is PointerDownEvent)
return onPointerDown(event);
if (onPointerMove != null && event is PointerMoveEvent)
return onPointerMove(event);
if (onPointerUp != null && event is PointerUpEvent)
return onPointerUp(event);
if (onPointerCancel != null && event is PointerCancelEvent)
return onPointerCancel(event);
if (onPointerSignal != null && event is PointerSignalEvent)
return onPointerSignal(event);
}
这里的handleEvent
主要是一层包装,回调的是外部传进来的接口。我们知道Widget和RenderObject是存在一定的对应关系的,接下来,我们就需要找到GestureDetector
和RenderPointerListener
之间的关系。
/// [GestureDetector]
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
) {
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;
},
);
}
if (onDoubleTap != null) {
gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
() => DoubleTapGestureRecognizer(debugOwner: this),
(DoubleTapGestureRecognizer instance) {
instance.onDoubleTap = onDoubleTap;
},
);
}
if (onLongPress != null ||
onLongPressUp != null ||
onLongPressStart != null ||
onLongPressMoveUpdate != null ||
onLongPressEnd != null) {
gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
() => LongPressGestureRecognizer(debugOwner: this),
(LongPressGestureRecognizer instance) {
instance
..onLongPress = onLongPress
..onLongPressStart = onLongPressStart
..onLongPressMoveUpdate = onLongPressMoveUpdate
..onLongPressEnd =onLongPressEnd
..onLongPressUp = onLongPressUp;
},
);
}
/// ......省略部分代码......
return RawGestureDetector(
gestures: gestures,
behavior: behavior,
excludeFromSemantics: excludeFromSemantics,
child: child,
);
}
GestureDetector
的build
方法中根据外部设置的回调,创建了大量的手势识别器对象,这些手势识别器都是GestureRecognizer
的子类。GestureRecognizer
类提供了一个基本的API,可以被那些与手势识别器一起工作的类使用,但不关心手势识别器本身的具体细节。最终,GestureDetector
将这些手势识别器传入RawGestureDetector
中包装。
RawGestureDetector
继承自StatefulWidget
,我们查看RawGestureDetectorState.build
方法
/// [RawGestureDetector]
@override
Widget build(BuildContext context) {
Widget result = Listener(
onPointerDown: _handlePointerDown,
behavior: widget.behavior ?? _defaultBehavior,
child: widget.child,
);
if (!widget.excludeFromSemantics)
result = _GestureSemantics(
child: result,
assignSemantics: _updateSemanticsForRenderObject,
);
return result;
}
RawGestureDetector
内部又使用Listener
包装了一层,并将_handlePointerDown
传递给它的onPointerDown
回调
/// [Listener]
Widget build(BuildContext context) {
Widget result = _child;
if (onPointerEnter != null ||
onPointerExit != null ||
onPointerHover != null) {
result = MouseRegion(
onEnter: onPointerEnter,
onExit: onPointerExit,
onHover: onPointerHover,
opaque: false,
child: result,
);
}
result = _PointerListener(
onPointerDown: onPointerDown,
onPointerUp: onPointerUp,
onPointerMove: onPointerMove,
onPointerCancel: onPointerCancel,
onPointerSignal: onPointerSignal,
behavior: behavior,
child: result,
);
return result;
}
在Listener
中,又再次将onPointerDown
透传给_PointerListener
类,而在该类中,创建了RenderPointerListener
对象。最终,将RawGestureDetector
中的_handlePointerDown
回调传递给了RenderPointerListener
的handleEvent
调用,到这里我们完成了GestureDetector
控件到RenderObject.handleEvent
方法的闭环。
/// [_PointerListener]
RenderPointerListener createRenderObject(BuildContext context) {
return RenderPointerListener(
onPointerDown: onPointerDown,
onPointerMove: onPointerMove,
onPointerUp: onPointerUp,
onPointerCancel: onPointerCancel,
onPointerSignal: onPointerSignal,
behavior: behavior,
);
}
事件的竞争
在事件处理时,一个常见的问题是,当同一个区域内有多个控件可以接受触摸时,应该将事件交给谁来处理呢?这就涉及到Flutter的事件竞争机制。
回到RawGestureDetector
的_handlePointerDown
方法
void _handlePointerDown(PointerDownEvent event) {
assert(_recognizers != null);
for (final GestureRecognizer recognizer in _recognizers.values)
recognizer.addPointer(event);
}
GestureRecognizer
的addPointer
方法用于注册一个可能与这个手势检测器相关的新指针。这个手势识别器的所有者调用addPointer()
,其中包含了每个应该被考虑用于这个手势的PointerDownEvent
。然后,手势识别器的责任是将自己添加到全局指针路由器(见PointerRouter
),以接收这个指针的后续事件,并将该指针添加到全局手势竞技场管理器(见GestureArenaManager
),以跟踪该指针。
也就是说,只有通过addPointer
之后才能参与竞争。
/// [GestureRecognizer]
void addPointer(PointerDownEvent event) {
_pointerToKind[event.pointer] = event.kind;
if (isPointerAllowed(event)) {
addAllowedPointer(event);
} else {
handleNonAllowedPointer(event);
}
}
在GestureRecognizer
中,addPointer
实际上调用的是子类的addAllowedPointer
方法。由于不同手势识别器的处理逻辑不同,这里我们分析最简单的Tap手势,则按照继承关系,进入Tap手势识别器的基类
/// [BaseTapGestureRecognizer]
void addAllowedPointer(PointerDownEvent event) {
if (state == GestureRecognizerState.ready) {
_down = event;
}
if (_down != null) {
super.addAllowedPointer(event); // 调用父类
}
}
void startTrackingPointer(int pointer, [Matrix4 transform]) {
// 调用父类
super.startTrackingPointer(pointer, transform);
}
这里主要调用了其父类方法,单个指针手势识别器基类PrimaryPointerGestureRecognizer
/// [PrimaryPointerGestureRecognizer]
void addAllowedPointer(PointerDownEvent event) {
// 开始跟踪指针事件,实际上调用的是OneSequenceGestureRecognizer中的实现
startTrackingPointer(event.pointer, event.transform);
if (state == GestureRecognizerState.ready) {
// 将手势识别器状态改为 possible
state = GestureRecognizerState.possible;
primaryPointer = event.pointer;
initialPosition = OffsetPair(local: event.localPosition, global: event.position);
if (deadline != null)
_timer = Timer(deadline, () => didExceedDeadlineWithEvent(event));
}
}
首先调用了OneSequenceGestureRecognizer
中实现的startTrackingPointer
方法
/// [OneSequenceGestureRecognizer]
void startTrackingPointer(int pointer, [Matrix4 transform]) {
// 将自己添加到全局指针路由器,由GestureBinding的handleEvent处理
GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent, transform);
_trackedPointers.add(pointer);
assert(!_entries.containsValue(pointer));
_entries[pointer] = _addPointerToArena(pointer);
}
// 将事件加入竞技场
GestureArenaEntry _addPointerToArena(int pointer) {
if (_team != null)
return _team.add(pointer, this);
return GestureBinding.instance.gestureArena.add(pointer, this);
}
这里调用GestureBinding
成员GestureArenaManager
的add
方法,将手势识别器加入竞技场
/// 在竞技场中添加一个新成员(如手势识别器)
GestureArenaEntry add(int pointer, GestureArenaMember member) {
final _GestureArena state = _arenas.putIfAbsent(pointer, () {
return _GestureArena();
});
state.add(member);
return GestureArenaEntry._(this, pointer, member);
}
这里有几个类需要说明一下,理顺逻辑
-
GestureArenaManager
:手势竞技管理器,它管理了整个竞争的过程,胜出的条件是 ,第一个竞技获胜的成员或最后一个不被拒绝的成员 -
GestureArenaEntry
:封装手势事件竞技信息的实体,包含参与事件竞技的成员 -
GestureArenaMember
:代表一个参加竞技场的对象。从_GestureArena
接收回调,以在手势竞争中获胜或失败时通知该对象。 无论此成员被解决的原因是什么,此成员被添加到的每个竞技场都会调用acceptGesture
或rejectGesture
中的一个。 例如,如果成员自己决胜了竞技场,则该成员仍会收到acceptGesture
回调。GestureRecognizer
类将它作为接口实现,因此竞技场的竞争其实就是GestureRecognizer
之间的竞争。 -
_GestureArena
:是GestureArenaManager
内的竞技场。它内部持有一个GestureArenaMember
列表,官方解释如果有成员在竞技场开放时试图获胜,则成为 “急切获胜者”。当竞技场对新的参与者关闭时,我们会寻找一个"急切获胜者",如果有一个,那么我们会在那个时候解决这个问题。
class GestureArenaManager {
final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};
/// ......省略部分代码......
}
class _GestureArena {
final List<GestureArenaMember> members = <GestureArenaMember>[];
bool isOpen = true;
bool isHeld = false;
bool hasPendingSweep = false;
GestureArenaMember eagerWinner;
void add(GestureArenaMember member) {
assert(isOpen);
members.add(member);
}
}
abstract class GestureArenaMember {
/// 获胜时调用
void acceptGesture(int pointer);
/// 失败时调用
void rejectGesture(int pointer);
}
class GestureArenaEntry {
GestureArenaEntry._(this._arena, this._pointer, this._member);
final GestureArenaManager _arena;
final int _pointer;
final GestureArenaMember _member;
/// 调用该成员要求胜利(含接受)或承认失败(含拒绝)
/// 试图解决一个已经解决的竞技场的手势识别器是可以的
void resolve(GestureDisposition disposition) {
_arena._resolve(_pointer, _member, disposition);
}
}
最后调用的是GestureBinding
中的handleEvent
/// [GestureBinding]
void handleEvent(PointerEvent event, HitTestEntry entry) {
// 触发指针事件路由器。即调用手势识别器中的 handleEvent方法
// 此处通常不会处理 PointerDownEvent 事件
pointerRouter.route(event);
if (event is PointerDownEvent) {
// 关闭该Down事件的竞技,尝试获得胜利。如果没有则继续到 MOVE 或者 UP中决胜
gestureArena.close(event.pointer);
} else if (event is PointerUpEvent) {
// 到 UP 事件了,强行解决竞技场的问题,让第一个成员获胜。
gestureArena.sweep(event.pointer);
} else if (event is PointerSignalEvent) {
pointerSignalResolver.resolve(event);
}
}
这里是通过PointerDownEvent
和PointerUpEvent
分别来控制竞技场的关闭和清扫。
/// [GestureArenaManager]
// 关闭该Down事件的竞技,尝试获得胜利
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;
_tryToResolveArena(pointer, state);
}
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);
}
void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {
_arenas.remove(pointer);
for (final GestureArenaMember rejectedMember in state.members) {
if (rejectedMember != member)
rejectedMember.rejectGesture(pointer);
}
member.acceptGesture(pointer);
}
// 清扫竞技场,强制让第一个成员获胜
void sweep(int pointer) {
final _GestureArena state = _arenas[pointer];
if (state == null)
return; // This arena either never existed or has been resolved.
if (state.isHeld) {
state.hasPendingSweep = true;
return; // This arena is being held for a long-lived member.
}
_arenas.remove(pointer);
if (state.members.isNotEmpty) {
// 第一个成员获胜。
state.members.first.acceptGesture(pointer);
// 把坏消息告诉所有其他成员。
for (int i = 1; i < state.members.length; i++)
state.members[i].rejectGesture(pointer);
}
}
只有一个成员时,直接胜利,响应后续事件。否则在Down事件中无法决胜,留待Up事件处理,即走sweep
方法
/// [BaseTapGestureRecognizer]
void acceptGesture(int pointer) {
super.acceptGesture(pointer);
if (pointer == primaryPointer) {
// 调用TapGestureRecognizer中的handleTapDown方法 ,处理onTapDown回调
_checkDown();
_wonArenaForPrimaryPointer = true;
// 调用TapGestureRecognizer中的handleTapUp方法 ,处理onTapUp或onTap回调
_checkUp();
}
}
事件拦截
Flutter框架给我们提供了两个Widget
来处理事件拦截。
-
AbsorbPointer
是一个在命中测试期间吸收指针事件的控件,当
absorbing
为true时,此控件通过自身终止命中测试来防止其子树接收指针事件。 它仍然在布局过程中占用空间,并像往常一样绘制其子级。 它只是防止其子级成为定位事件的目标,而它本身可以响应事件 -
IgnorePointer
是一个在命中测试时不可见的控件。当
ignoring
为真时,该控件(和它的子树)在命中测试中是不可见的。它在布局时仍会消耗空间,并像往常一样绘制它的子节点。它只是不能成为定位事件的目标,而它本身无法响应事件
flutter\lib\src\widgets\basic.dart
/// [AbsorbPointer]
class AbsorbPointer extends SingleChildRenderObjectWidget {
const AbsorbPointer({
Key key,
this.absorbing = true,
Widget child,
this.ignoringSemantics,
}): assert(absorbing != null),
super(key: key, child: child);
RenderAbsorbPointer createRenderObject(BuildContext context) {
return RenderAbsorbPointer(
absorbing: absorbing,
ignoringSemantics: ignoringSemantics,
);
}
}
flutter\lib\src\rendering\proxy_box.dart
/// [RenderAbsorbPointer]
class RenderAbsorbPointer extends RenderProxyBox {
RenderAbsorbPointer({
RenderBox child,
bool absorbing = true,
bool ignoringSemantics,
}) : assert(absorbing != null),
_absorbing = absorbing,
_ignoringSemantics = ignoringSemantics,
super(child);
@override
bool hitTest(BoxHitTestResult result, { Offset position }) {
return absorbing
? size.contains(position)
: super.hitTest(result, position: position);
}
}
视频课程
如需要获取完整的Flutter全栈式开发课程,请访问以下地址
Flutter全栈式开发之Dart 编程指南
上一篇: php微信公众号开发之微信企业付款给个人
下一篇: 夏季最适合煲七款汤 解暑降温有益身体健康