不論是原生Android、iOS仍是JavaScript,只要是涉及手勢交互都會有事件的分發處理。和原生Android、iOS的事件分發的步驟和原理同樣,Flutter的事件分發整體也由手勢觸發、攔截和響應等幾個部分構成。Flutter全部事件源頭是 hooks.dart文件的_dispatchPointerDataPacket函數,經過攔截屏幕的點擊、滑動等各類事件,進而分發給原生代碼進行響應(ps:Android事件分發)。html
若是你看過了解原生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包中,以下圖所示。java
Flutter的事件分發基類是GestureBinding,打開GestureBinding類,它的成員函數包括dispatchEvent、handleEvent和hitTes等,主要是從事件隊列裏按照先入先出方式處理PointerEvent,源碼以下。node
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget { @override void initInstances() { super.initInstances(); _instance = this; ui.window.onPointerDataPacket = _handlePointerDataPacket; }
其中,WidgetsFlutterBinding.ensureInitialized()函數的做用就是初始化各個binging。緩存
和Android、iOS相似,Flutter的事件分發的入口在runApp函數,相關的代碼以下。app
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事件分發的起點。ide
下面咱們來重點看一下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方法,源碼以下。post
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 事件分發