浅谈Flutter核心机制之--- 事件分发
Posted 一叶飘舟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈Flutter核心机制之--- 事件分发相关的知识,希望对你有一定的参考价值。
前言
在Flutter中统的事件通过Engine层发送到framework层,GestureBinding负责framework层事件的处理,接下来由GestureBinding为入口来进行分析。
设备事件信号分类
一、Flutter对设备事件定义了touch 、mouse、stylus 、invertedStylus、unknown几种,其中手机端屏幕触摸产生的事件对应的是touch类型。其他还有手写笔 , 鼠标 等设备。
enum PointerDeviceKind
/// A touch-based pointer device.
touch,
/// A mouse-based pointer device.
mouse,
/// A pointer device with a stylus.
stylus,
/// A pointer device with a stylus that has been inverted.
invertedStylus,
/// An unknown pointer device.
unknown
二、以上事件源的数据通过PointerEventConverter#expand方法转换,最终体现到Flutter业务端的事件都会抽象成PointerEvent
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.
1,转换数据成PointerEvent
_pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
if (!locked)
2,处理事件
_flushPointerEventQueue();
事件类型
Flutter中对屏幕触发的事件定义了以下类型:
- PointerAddedEvent : 接触屏幕(获取事件,创建了事件追踪器)
- PointerHoverEvent :悬停事件(前面已经创建了事件追踪器,再次使用事件追踪器,与PointerAddedEvent互斥)
- PointerDownEvent :按下事件(发生在PointerAddedEvent或PointerHoverEvent之后)
- PointerMoveEvent :滑动事件
- PointerUpEvent : 完全离开屏幕事件
- PointerCancelEvent : 事件取消(事件追踪器被断开,比如正在滑动时弹出Dialog焦点变化导致事件取消,与PointerAddedEvent成对)
- PointerRemovedEvent : 事件可能已偏离设备的检测范围,或者可能已完全与系统断开连接。
三.处理事件刷新事件队列
void _flushPointerEventQueue()
assert(!locked);
//处理队列里面所有的事件
while (_pendingPointerEvents.isNotEmpty)
handlePointerEvent(_pendingPointerEvents.removeFirst());
void handlePointerEvent(PointerEvent event)
...
_handlePointerEventImmediately(event);
void _handlePointerEventImmediately(PointerEvent event)
HitTestResult? hitTestResult;
1. if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent)
assert(!_hitTests.containsKey(event.pointer));
hitTestResult = HitTestResult();
2. hitTest(hitTestResult, event.position);
if (event is PointerDownEvent)
_hitTests[event.pointer] = hitTestResult;
3. else if (event is PointerUpEvent || event is PointerCancelEvent)
hitTestResult = _hitTests.remove(event.pointer);
4. else if (event.down)
// Because events that occur with the pointer down (like
// [PointerMoveEvent]s) should be dispatched to the same place that their
// initial PointerDownEvent was, we want to re-use the path we found when
// the pointer went down, rather than do hit detection each time we get
// such an event.
hitTestResult = _hitTests[event.pointer];
if (hitTestResult != null ||
event is PointerAddedEvent ||
event is PointerRemovedEvent)
5. dispatchEvent(event, hitTestResult);
-
down和hover事件会创建一个HitTestResult,如果是down事件用集合存起来(与后续的move,up事件进行完整的事件分发)
-
这里的从hitTest方法被RendererBinding重写了,根部进行hitTest,添加一个根部
@override void hitTest(HitTestResult result, Offset position) assert(renderView != null); assert(result != null); assert(position != null); //renderView对事件进行hitTest冒泡 renderView.hitTest(result, position: position); //调用super添加一个根部的HitTestEntry节点 super.hitTest(result, position);
renderView.hitTest
bool hitTest(HitTestResult result, required Offset position ) if (child != null) //对child进行层级遍历hitTest,对命中的会 result.add(HitTestEntry());进行事件处理 child!.hitTest(BoxHitTestResult.wrap(result), position: position); //对命中的创建一个HitTestEntry进行事件处理 result.add(HitTestEntry(this)); return true;
-
up事件会移除集合中的HitTestResult,此次事件链路结束。
-
这里的event.down指的是PointerMoveEvent
-
dispatchEvent事件分发
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult)
assert(!locked);
// No hit test information implies that this is a [PointerHoverEvent],
// [PointerAddedEvent], or [PointerRemovedEvent]. These events are specially
// routed here; other events will be routed through the `handleEvent` below.
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)
,
));
事件分发流程图
小结
只有命中了才会添加一个HitTestEntry到HitTestResult中,最后接收事件处理执行handleEvent方法,handleEvent方法会把事件再分发给到手势识别器,从上面RenderObject中的hitest方法可以看出优先调用child中的hitest方法,这样可以让事件的优先处理权交给层级最深的child
手势识别事件
GestureBinding中的handleEvent方法是最后一个执行的,它里面主要做了手势事件竞技。
手势识别的成员:
-
GestureArenaManager 竞技管理者
-
pointer 一个int值, 从 down 到 up 完整的链路中pointer的值不会变
-
GestureArenaMember 竞技者(具体的手势识别器,如:TapGestureRecognizer,VerticalDragGestureRecognizer)
-
_GestureArena 竞技场 一个Pointer只创建一个
-
GestureArenaEntry 一个组合类,一个Pointer会创建多个记录事件的链路(对应GestureArenaManager.add)
GestureArenaManager
GestureArenaManager是一个管理类全局只创建了一个,它对外只暴露了五个方法:add,sweep,hold,close,release
- add :添加一个手势识别器(GestureArenaMember)
- close : 关闭手势竞技场,禁止新成员进入
- sweep:让_GestureArena(竞技场)中的第一个GestureArenaMember(手势识别器)获胜,当 _GestureArena中hold = true 时则延时让第一个获胜
- hold : 标记该point为hold
- release:清除hold标记,并且强制让第一个获胜(同sweep)
1
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);
2
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);
3
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)
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);
4
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));
5
void release(int pointer)
final _GestureArena? state = _arenas[pointer];
if (state == null)
return; // This arena either never existed or has been resolved.
state.isHeld = false;
assert(_debugLogDiagnostic(pointer, 'Releasing', state));
if (state.hasPendingSweep)
sweep(pointer);
GestureArenaMember
GestureArenaMember是手势识别器的顶级接口类,在竞技结束后如果当前识别器抢夺成功会调用acceptGesture方法失败则调用rejectGesture方法
abstract class GestureArenaMember
/// Called when this member wins the arena for the given pointer id.
void acceptGesture(int pointer);
/// Called when this member loses the arena for the given pointer id.
void rejectGesture(int pointer);
复制代码
GestureArenaEntry
记录framework每一次pointer事件的回调,回调一次就创建一个GestureArenaEntry
class GestureArenaEntry
GestureArenaEntry._(this._arena, this._pointer, this._member);
final GestureArenaManager _arena;
final int _pointer;
final GestureArenaMember _member;
/// Call this member to claim victory (with accepted) or admit defeat (with rejected).
///
/// It's fine to attempt to resolve a gesture recognizer for an arena that is
/// already resolved.
void resolve(GestureDisposition disposition)
_arena._resolve(_pointer, _member, disposition);
_GestureArena
_GestureArena竞技场,里面管理当前pointer所有的GestureArenaMember(竞技者,具体的手势识别器),以及一个获胜者eagerWinner
RenderObject
所有手势识别器竞技都是直接或者间接通过RenderObject#handleEvent方法触发的,前面我们讲了事件分发的流程它会调用GestureRecognizer#addPointer方法,这里以RangeSlider为例子,RangeSlider对应的RenderObject为_RenderRangeSlider
@override
void handleEvent(PointerEvent event, HitTestEntry entry)
assert(debugHandleEvent(event, entry));
if (event is PointerDownEvent && isEnabled)
// We need to add the drag first so that it has priority.
_drag.addPointer(event);
_tap.addPointer(event);
复制代码
上面的处理方式是优先让滑动事件的手势识别器优先进入竞技场
- addPointer
void addPointer(PointerDownEvent event)
_pointerToKind[event.pointer] = event.kind;
if (isPointerAllowed(event))
addAllowedPointer(event);
else
handleNonAllowedPointer(event);
复制代码
- isPointerAllowed
isPointerAllowed方法会被具体的手势识别器重写,根据具体的业务逻辑判断是否跟踪此事件
-
PointerRouter#addRoute
在isPointerAllowed返回true时候最终都会调用PointerRouter#addRoute方法
@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);
复制代码
-
GestureBinding#handleEvent触发路由
从上面得知最后一个HitTestTarget就是GestureBinding,所以它在事件事件分发的末端
@override // from HitTestTarget 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调用路由
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); ); void _dispatch(PointerEvent event, PointerRoute route, Matrix4? transform) try event = event.transformed(transform); route(event); catch (exception, stack)
-
GestureRecognizer#handleEvent
route对应手势识别器中handleEvent方法
整体流程如下图:
-
总结
Flutter事件是先分发到RenderObject中,且RenderObject的层级越深处理的优先级越高(参考hitTest方法)。RenderOnject再把具体事件注册到到手势识别器中接着参与手势竞技,GestureBinding会让还在竞技场中的第一个手势识别器获得胜利。
以上是关于浅谈Flutter核心机制之--- 事件分发的主要内容,如果未能解决你的问题,请参考以下文章
Android 进阶——Framework 核心之Touch事件分发机制详细攻略