Flutter事件分发源码剖析
2020/9/28 5:04:03
本文主要是介绍Flutter事件分发源码剖析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
概述
不管是原生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 事件分发
这篇关于Flutter事件分发源码剖析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值