共计 15768 个字符,预计需要花费 40 分钟才能阅读完成。
概述
不论是原生 Android、iOS 还是 JavaScript,只有是波及手势交互都会有事件的散发解决。和原生 Android、iOS 的事件散发的步骤和原理一样,Flutter 的事件散发总体也由手势触发、拦挡和响应等几个局部形成。Flutter 所有事件源头是 hooks.dart 文件的_dispatchPointerDataPacket 函数,通过拦挡屏幕的点击、滑动等各种事件,进而分发给原生代码进行响应(ps:Android 事件散发)。
如果你看过理解原生 Android、iOS 的事件散发机制,那么 Flutter 的事件散发,其实是在 Android 和 iOS 上加了壳,即 Flutter 的事件散发是在原生 Android、iOS 的的事件散发上进行包装的(Android – C – Dart,iOS- C -Dart)。其中,C 是 Flutter 的底层 engine,负责 Flutter 下层和原生 Android、iOS 零碎的交互。
事件散发到 Dart 的入口类是 GestureBinding 类,此类位于 gestures/binding.dart 文件中,与手势辨认相干的都位于 gestures 包中,如下图所示。
- converter.dart 将物理坐标_dispatchPointerDataPacket 收到的物理数据 PointerDataPacket 转换成 PointerEvent,相似于安卓在 ViewRootImpl.java 将 InputEventReceiver 收到的 InputEvent 转换为 MotionEvent。
- recognizer.dart 的 GestureRecognizer 是所有手势辨认的基类。
- rendering/binding.dart 的 RendererBinding 类关联了 render 树和 Flutter 引擎,等价于安卓的 Surface。
- view.dart 的 RenderView 是 render 树的根节点,等价于安卓的 DecorView。
Flutter 的事件散发基类是 GestureBinding,关上 GestureBinding 类,它的成员函数包含 dispatchEvent、handleEvent 和 hitTes 等,次要是从事件队列里依照先入先出形式解决 PointerEvent,源码如下。
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
@override
void initInstances() {super.initInstances();
_instance = this;
ui.window.onPointerDataPacket = _handlePointerDataPacket;
}
其中,WidgetsFlutterBinding.ensureInitialized()函数的作用就是初始化各个 binging。
Flutter 事件散发
和 Android、iOS 相似,Flutter 的事件散发的入口在 runApp 函数,相干的代码如下。
void runApp(Widget app) {WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();}
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {static WidgetsBinding ensureInitialized() {if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget
).attachToRenderTree(buildOwner, renderViewElement);
}
WidgetsFlutterBinding.ensureInitialized()函数的作用是初始化各个 binging。事实上,Flutter 中的 WidgetsFlutterBinding 的 Binding 能够分为 GestureBinding、ServicesBinding、SchedulerBinding、PaintingBinding、SemanticsBinding、RendererBinding、WidgetsBinding 等 7 种 Binding,它们都有本人在性能上的划分。其中,GestureBinding 就是处理事件散发的,attachRootWidget 就是设置根节点,能够看到真正的根节点是 renderview,也是 Flutter 事件散发的终点。
上面咱们来重点看一下 GestureBinding 类。
GestureBinding
和 Android 事件处理的流程一样,首先,零碎会拦挡用户的事件,而后在应用 GestureBinding 的_handlePointerEvent 进行事件命中解决。原生事件达到 Dart 层之后调用的第一个办法是_handlePointerDataPacket,它的源码如下。
void _handlePointerDataPacket(ui.PointerDataPacket packet) {_pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
if (!locked)
_flushPointerEventQueue();}
_handlePointerDataPacket 办法有一个 PointerEventConverter 类,作用是将原生传来的手势数据全副转化为 Dart 对应的对象保留数据,而后保留到汇合中进行贮存。接下来来咱们看一下_flushPointerEventQueue 办法,源码如下。
void _flushPointerEventQueue() {assert(!locked);
while (_pendingPointerEvents.isNotEmpty)
_handlePointerEvent(_pendingPointerEvents.removeFirst());
}
_flushPointerEventQueue 办法的作用就是循环解决每个手指的的事件,并进行解决,源码如下。
void _handlePointerEvent(PointerEvent event) {assert(!locked);
HitTestResult hitTestResult;
// 如果是手指按下的话
if (event is PointerDownEvent || event is PointerSignalEvent) {assert(!_hitTests.containsKey(event.pointer));
hitTestResult = HitTestResult();
// 失去碰撞的控件组
hitTest(hitTestResult, event.position);
if (event is PointerDownEvent) {_hitTests[event.pointer] = hitTestResult;
}
assert(() {if (debugPrintHitTestResults)
debugPrint('$event: $hitTestResult');
return true;
}());
}
// 手指抬起
else if (event is PointerUpEvent || event is PointerCancelEvent) {hitTestResult = _hitTests.remove(event.pointer);
}
// 缓存点击的事件,接下来产生滑动的时候间接复用原来的碰撞控件组
else if (event.down) {
// Because events that occur with the pointer down (like
// PointerMoveEvents) 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];
}
assert(() {if (debugPrintMouseHoverEvents && event is PointerHoverEvent)
debugPrint('$event');
return true;
}());
if (hitTestResult != null ||
event is PointerHoverEvent ||
event is PointerAddedEvent ||
event is PointerRemovedEvent) {dispatchEvent(event, hitTestResult);
}
}
这个办法的次要目标就是失去 HitTestResult,就是依据按下的坐标地位找出 view 树中哪些控件在点击的范畴内,手指在挪动和抬起的时候都复用以后的事件,区别在于不同的手指有不同的索引值。接下来,看一下用户的触摸行为,hitTest 首先会进入 RendererBinding 解决,关上 RendererBinding 类的 hitTest 办法,如下所示。
RenderView get renderView => _pipelineOwner.rootNode as RenderView;
void hitTest(HitTestResult result, Offset position) {assert(renderView != null);
renderView.hitTest(result, position: position);
super.hitTest(result, position);
}
其中,RenderView 能够了解为 Flutter 视图树的根 View,在 Flutter 中也叫做 Widget,一个 Widget 对应一个 Element。在 Flutter 中,渲染会三棵树,即 Widget 树、Element 树和 RenderObject 树。咱们进行页面布局剖析时,就能够看到它们,如下所示。
对于 Widget 树、Element 树和 RenderObject 树,能够查看 Flutter 渲染之 Widget、Element 和 RenderObject 的介绍。
而后,咱们关上 renderView.hitTest 办法,对应的代码如下所示。
bool hitTest(HitTestResult result, { Offset position}) {if (child != null)
child.hitTest(BoxHitTestResult.wrap(result), position: position);
result.add(HitTestEntry(this));
return true;
}
能够看到,根视图是先从子 view 开始放进汇合,放完子 view 再放本人,这和前端 JS 点击事件冒泡的原理是一样的。并且,只有满足条件子视图才会放到 入 RenderBox 的这个办法中。
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;
}
接下来,看一下 Stack 小部件 hitTestChildren 的实现,源码如下。
@override
bool hitTestChildren(BoxHitTestResult result, { Offset position}) {return defaultHitTestChildren(result, position: position);
}
bool defaultHitTestChildren(BoxHitTestResult result, { Offset position}) {
// the x, y parameters have the top left of the node's box as the origin
ChildType child = lastChild;
while (child != null) {
final ParentDataType childParentData = child.parentData;
final bool isHit = result.addWithPaintOffset(
offset: childParentData.offset,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {assert(transformed == position - childParentData.offset);
return child.hitTest(result, position: transformed);
},
);
if (isHit)
return true;
child = childParentData.previousSibling;
}
return false;
}
这个办法的作用就是判断蕴含 Padding 的视图是否在点击范畴内,如果命中,则阻止其余事件持续冒泡。看到此处,咱们大体能够看出,Flutter 的事件处理次要是判断点击的坐标知否在控件范畴内,如果在范畴内间接响应,如果不在持续向上冒泡,并且事件是从叶子开始的,也即 Web 中的事件冒泡。
实现命中解决后,接下来回到事件处理的主流程,即事件派发 dispatchEvent,代码位于 gestrues/binding 外面,源码如下。
void dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {assert(!locked);
// No hit test information implies that this is a hover or pointer
// add/remove event. 这种状况出在指针悬停屏幕上方,微微接触或不接触,是手机敏感而言
if (hitTestResult == null) {assert(event is PointerHoverEvent || event is PointerAddedEvent || event is PointerRemovedEvent);
try {pointerRouter.route(event);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetailsForPointerEventDispatcher(
exception: exception,
stack: stack,
library: 'gesture library',
context: ErrorDescription('while dispatching a non-hit-tested pointer event'),
event: event,
hitTestEntry: null,
informationCollector: () sync* {yield DiagnosticsProperty<PointerEvent>('Event', event, style: DiagnosticsTreeStyle.errorProperty);
},
));
}
return;
}
for (HitTestEntry entry in hitTestResult.path) {
try {entry.target.handleEvent(event.transformed(entry.transform), entry);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetailsForPointerEventDispatcher(
exception: exception,
stack: stack,
library: 'gesture library',
context: ErrorDescription('while dispatching a pointer event'),
event: event,
hitTestEntry: entry,
informationCollector: () sync* {yield DiagnosticsProperty<PointerEvent>('Event', event, style: DiagnosticsTreeStyle.errorProperty);
yield DiagnosticsProperty<HitTestTarget>('Target', entry.target, style: DiagnosticsTreeStyle.errorProperty);
},
));
}
}
}
此办法最基本的作用是循环事件散发,并以冒泡的模式从底部到散发事件,当事件被命中时,即由以后子节点处理事件,这和 Android 的事件散发的逻辑是一样的。上面以 GestureDetector 和 Listener 来举例事件散发的不同。如果用 Listener 的话,Listener 的组件最终对应的 RenderObject 是 RenderPointerListener,它的监测以后点击是否命中的办法如下。
bool hitTest(BoxHitTestResult result, { Offset position}) {
bool hitTarget = false;
if (size.contains(position)) {hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position);
if (hitTarget || behavior == HitTestBehavior.translucent)
result.add(BoxHitTestEntry(this, position));
}
return hitTarget;
}
@override
bool hitTestSelf(Offset position) => behavior == HitTestBehavior.opaque;
应用 Listener 嵌套的子组件默认状况下是命中的,很多子部件例如 Text
、Image
等,它们的 hitTestSelf 返回 True,如果咱们为 Text 嵌套了 Listener,那么事件散发的时候设计的代码如下所示。
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);
}
如果应用的是 GestureDetector 的话,build 办法会为咱们增加很多解决手势的办法类,如TapGestureRecognizer
,通过解决手势辨认后,最终返回的是RawGestureDetector
,波及的代码如下。
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
if (
onTapDown != null ||
onTapUp != null ||
onTap != null ||
onTapCancel != 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
..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;
},
);
}
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;
},
);
}
if (onHorizontalDragDown != null ||
onHorizontalDragStart != null ||
onHorizontalDragUpdate != null ||
onHorizontalDragEnd != null ||
onHorizontalDragCancel != null) {gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(() => HorizontalDragGestureRecognizer(debugOwner: this),
(HorizontalDragGestureRecognizer instance) {
instance
..onDown = onHorizontalDragDown
..onStart = onHorizontalDragStart
..onUpdate = onHorizontalDragUpdate
..onEnd = onHorizontalDragEnd
..onCancel = onHorizontalDragCancel
..dragStartBehavior = dragStartBehavior;
},
);
}
if (onPanDown != null ||
onPanStart != null ||
onPanUpdate != null ||
onPanEnd != null ||
onPanCancel != null) {gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(() => PanGestureRecognizer(debugOwner: this),
(PanGestureRecognizer instance) {
instance
..onDown = onPanDown
..onStart = onPanStart
..onUpdate = onPanUpdate
..onEnd = onPanEnd
..onCancel = onPanCancel
..dragStartBehavior = dragStartBehavior;
},
);
}
if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(() => ScaleGestureRecognizer(debugOwner: this),
(ScaleGestureRecognizer instance) {
instance
..onStart = onScaleStart
..onUpdate = onScaleUpdate
..onEnd = onScaleEnd;
},
);
}
if (onForcePressStart != null ||
onForcePressPeak != null ||
onForcePressUpdate != null ||
onForcePressEnd != null) {gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(() => ForcePressGestureRecognizer(debugOwner: this),
(ForcePressGestureRecognizer instance) {
instance
..onStart = onForcePressStart
..onPeak = onForcePressPeak
..onUpdate = onForcePressUpdate
..onEnd = onForcePressEnd;
},
);
}
return RawGestureDetector(
gestures: gestures,
behavior: behavior,
excludeFromSemantics: excludeFromSemantics,
child: child,
);
并且,RawGestureDetector 默认应用的也是 Listener,它注册了手指按下的办法,散发的时候 Down 事件是 sdk 默认解决的。
void _handlePointerDown(PointerDownEvent event) {assert(_recognizers != null);
for (GestureRecognizer recognizer in _recognizers.values)
recognizer.addPointer(event);
}
此办法会向 Binding 路由器中注册那些须要解决的事件,如果咱们只申明了点击事件,那么汇合中负责增加的 GestureRecognizer 的实现类就是 TapGestureRecognizer,接下来咱们看一下 addPointer 办法。
void addPointer(PointerDownEvent event) {_pointerToKind[event.pointer] = event.kind;
if (isPointerAllowed(event)) {addAllowedPointer(event);
} else {handleNonAllowedPointer(event);
}
}
bool isPointerAllowed(PointerDownEvent event) {switch (event.buttons) {
case kPrimaryButton:
if (onTapDown == null &&
onTap == null &&
onTapUp == null &&
onTapCancel == null)
return false;
break;
case kSecondaryButton:
if (onSecondaryTapDown == null &&
onSecondaryTapUp == null &&
onSecondaryTapCancel == null)
return false;
break;
default:
return false;
}
return super.isPointerAllowed(event);
}
isPointerAllowed 办法的作用就是用来断定以后的手势,默认返回 false,如果事件比命中,接下来执行 addAllowedPointer 办法,如下所示。
void addAllowedPointer(PointerDownEvent event) {startTrackingPointer(event.pointer, event.transform);
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));
}
void startTrackingPointer(int pointer, [Matrix4 transform]) {GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent, transform);
_trackedPointers.add(pointer);
assert(!_entries.containsValue(pointer));
_entries[pointer] = _addPointerToArena(pointer);
}
这两个办法的次要作用就是用来将以后的 handleEvent 办法增加到 GestureBinding 路由器外面去,而_addPointerToArena 是就是增加处理事件的具体逻辑。接下来,咱们来看一下 GestureBinding 外面的 handleEvent 函数的事件散发逻辑。
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);
}
}
}
如果手指按下的时候 GestureRecognizer 的 handleEvent 办法没有决策出到底哪个控件会成为事件的解决者,那么会执行 gestureArena.close()办法,如下所示。
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);
}
如果未决策出哪个控件处理事件的时候,state.isOpen 此时被标记为 false,也即是敞开手势的解决。
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);
}
}
如果手势竞争中,有竞争胜出者,则由胜出者执行事件处理,如下所示。
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 (GestureArenaMember rejectedMember in state.members) {if (rejectedMember != member)
rejectedMember.rejectGesture(pointer);
}
member.acceptGesture(pointer);
}
如果事件处理中没有具体的事件处理对象,将会默认采纳最底层的的叶子节点控件作为事件处理者,也就是说最内层的那个控件将耗费事件。也就是说,如果应用 GestureRecognizer 来辨认手势事件时,最终事件会被最内层的 GestureRecognizer 耗费,这和 Android 单个控件耗费事件差不多,所以嵌套滚动总是先滚动内层,先被内层耗费,而后再执行外层。
参考:Flutter 事件散发