Touch

原理图

graph TB
subgraph 3Down事件添加gestureRecognizer到pointerRouter,后续才会分发过来
pointerRouter.addRoute
end

subgraph 4分发pointerRouter.addRoute添加进来的监听
pointerRouter.route_event
end

subgraph 1Down事件时确定HitTestResult
PointerDownEvent
end

subgraph 2发给HitTestResult每个元素
PointerAllEvent
end

PointerDownEvent-->|hitTest|HitTestResult-->HitTestTarget1
HitTestResult-->|RenderPointerListener.handleEvent|RawGestureDetectorState._handlePointerDown-->|addPointer_event|gestureRecognizer1
HitTestResult-->HitTestTargetXxx
HitTestResult-->|handleEvent|GestureBinding.handleEvent-->pointerRouter.route_event
GestureBinding.handleEvent-->|Down?|gestureArena.close
GestureBinding.handleEvent-->|Up?|gestureArena.sweep
RawGestureDetectorState._handlePointerDown-->|addPointer_event|gestureRecognizerXxx-->|Down|pointerRouter.addRoute
gestureRecognizerXxx-->|Down|gestureArena.add
PointerAllEvent-->|dispatchEvent|HitTestResult

类设计

GestureBinding.initInstances

/// A binding for the gesture subsystem.
//mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget
@override
 void initInstances() {
  super.initInstances();
  _instance = this;
  window.onPointerDataPacket = _handlePointerDataPacket;
 }

GestureBinding._handlePointerDataPacket

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.
  _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
  if (!locked)
   _flushPointerEventQueue();
 }

void _flushPointerEventQueue() {
  assert(!locked);
  while (_pendingPointerEvents.isNotEmpty)
   _handlePointerEvent(_pendingPointerEvents.removeFirst());
 }

GestureBinding._handlePointerEvent

void _handlePointerEvent(PointerEvent event) {
 HitTestResult hitTestResult;
 if (event is PointerDownEvent || event is PointerSignalEvent) {
  assert(!_hitTests.containsKey(event.pointer));
  hitTestResult = HitTestResult();
  hitTest(hitTestResult, event.position);//main
  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) {
  // 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];
 }

if (hitTestResult != null ||
   event is PointerHoverEvent ||
   event is PointerAddedEvent ||
   event is PointerRemovedEvent) {
  dispatchEvent(event, hitTestResult);//main
 }

hitTest

RendererBinding.hitTest

//GestureBinding
/// Determine which [HitTestTarget] objects are located at a given position.
 @override // from HitTestable
 void hitTest(HitTestResult result, Offset position) {
  result.add(HitTestEntry(this));
 }
//mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
 void hitTest(HitTestResult result, Offset position) {
  assert(renderView != null);
  renderView.hitTest(result, position: position);
  super.hitTest(result, position);
 }

RenderView.hitTest

//class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {
/// Determines the set of render objects located at the given position.
bool hitTest(HitTestResult result, { Offset position }) {
  if (child != null)
   child.hitTest(BoxHitTestResult.wrap(result), position: position);
  result.add(HitTestEntry(this));
  return true;
 }

RenderBox.hitTest

//RenderBox
/// Determines the set of render objects located at the given position.

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;
/// Override this method to check whether any children are located at the
 /// given position.

/// Used by [hitTest]. If you override [hitTest] and do not call this
 /// function, then you don't need to implement this function.
@protected
 bool hitTestChildren(BoxHitTestResult result, { Offset position }) => false;

/// Override this method if this render object can be hit even if its
 /// children were not hit.

/// The caller is responsible for transforming [position] from global
 /// coordinates to its location relative to the origin of this [RenderBox].
 /// This [RenderBox] is responsible for checking whether the given position is
 /// within its bounds.

/// Used by [hitTest]. If you override [hitTest] and do not call this
 /// function, then you don't need to implement this function.
 @protected
 bool hitTestSelf(Offset position) => false;

dispatchEvent

/// Dispatch an event to a hit test result's path.
@override // from HitTestDispatcher
 void dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {
  // No hit test information implies that this is a hover or pointer
  // add/remove event.
  if (hitTestResult == null) {
    pointerRouter.route(event);
    return;
  }

  for (HitTestEntry entry in hitTestResult.path) {
    try {
      entry.target.handleEvent(event.transformed(entry.transform), entry);//main
    } catch (exception, stack) {
    }
  }
 }

handleEvent

RenderPointerListener.handleEvent

renderpointerlistener

GestureBinding.handleEvent

@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(event)

/// A routing table for [PointerEvent] events.
   
  /// Calls the routes registered for this pointer event.
  ///
  /// Routes are called in the order in which they were added to the
  /// PointerRouter object.
  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);
  }

gestureArena.close

/// Prevents new members from entering the arena.
 /// Called after the framework has finished dispatching the pointer down event.
 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) {
  assert(_arenas[pointer] == state);
  assert(!state.isOpen);
  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;
  assert(members.length == 1);
  _arenas.remove(pointer);
  state.members.first.acceptGesture(pointer);
 }

void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {
 _arenas.remove(pointer);
  for (GestureArenaMember rejectedMember in state.members) {
   if (rejectedMember != member)
    rejectedMember.rejectGesture(pointer);
  }
  member.acceptGesture(pointer);
 }

gestureArena.sweep

/// Forces resolution of the arena, giving the win to the first member.
 ///
 /// Sweep is typically after all the other processing for a [PointerUpEvent]
 /// have taken place. It ensures that multiple passive gestures do not cause a
 /// stalemate that prevents the user from interacting with the app.
 ///
 /// Recognizers that wish to delay resolving an arena past [PointerUpEvent]
 /// should call [hold] to delay sweep until [release] is called.

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.
    }
    _arenas.remove(pointer);
    if (state.members.isNotEmpty) {
      // First member wins.
      assert(_debugLogDiagnostic(pointer, 'Winner: ${state.members.first}'));
      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);
    }
  }

GestureDetector

/// A widget that detects gestures.

/// If this widget has a child, it defers to that child for its sizing behavior.
/// If it does not have a child, it grows to fit the parent instead.
GestureDetector extends StatelessWidget {
  /// The widget below this widget in the tree.
  final Widget child;
  
  GestureDetector({
  Key key,
  this.child,
  this.onTapDown,
  this.onTapUp,
  this.onTap,
  this.onTapCancel,
  this.onSecondaryTapDown,
  this.onSecondaryTapUp,
  this.onSecondaryTapCancel,
  this.onDoubleTap,
  this.onLongPress,
  this.onLongPressStart,
  this.onLongPressMoveUpdate,
  this.onLongPressUp,
  this.onLongPressEnd,
  this.onVerticalDragDown,
  this.onVerticalDragStart,
  this.onVerticalDragUpdate,
  this.onVerticalDragEnd,
  this.onVerticalDragCancel,
  this.onHorizontalDragDown,
  this.onHorizontalDragStart,
  this.onHorizontalDragUpdate,
  this.onHorizontalDragEnd,
  this.onHorizontalDragCancel,
  this.onForcePressStart,
  this.onForcePressPeak,
  this.onForcePressUpdate,
  this.onForcePressEnd,
  this.onPanDown,
  this.onPanStart,
  this.onPanUpdate,
  this.onPanEnd,
  this.onPanCancel,
  this.onScaleStart,
  this.onScaleUpdate,
  this.onScaleEnd,
  this.behavior,
  this.excludeFromSemantics = false,
  this.dragStartBehavior = DragStartBehavior.start,
 }) 
  
}
@override
 Widget build(BuildContext context) {
  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>(//main
   		() => TapGestureRecognizer(debugOwner: this),
   		(TapGestureRecognizer instance) {
    		instance
     		..onTapDown = onTapDown
     		..onTapUp = onTapUp
     		..onTap = onTap
     		..onTapCancel = onTapCancel
     		..onSecondaryTapDown = onSecondaryTapDown
     		..onSecondaryTapUp = onSecondaryTapUp
     		..onSecondaryTapCancel = onSecondaryTapCancel;
  		 },
 		 );
		 }

  ......

  return RawGestureDetector(
    gestures: gestures,
    behavior: behavior,
    excludeFromSemantics: excludeFromSemantics,
    child: child,
   );

GestureRecognizerFactoryWithHandlers

/// Factory for creating gesture recognizers.
 ///
 /// `T` is the type of gesture recognizer this class manages.
 ///
 /// Used by [RawGestureDetector.gestures].

GestureRecognizerFactory<T extends GestureRecognizer> {
  /// Must return an instance of T.
  T constructor();
}
/// Factory for creating gesture recognizers that delegates to callbacks.
GestureRecognizerFactoryWithHandlers<T extends GestureRecognizer> {
 /// Signature for closures that implement [GestureRecognizerFactory.constructor].
 typedef GestureRecognizerFactoryConstructor<T extends GestureRecognizer> = T Function();

 /// Signature for closures that implement [GestureRecognizerFactory.initializer].
 typedef GestureRecognizerFactoryInitializer<T extends GestureRecognizer> = void Function(T instance);


/// Creates a gesture recognizer factory with the given callbacks.
 ///
 /// The arguments must not be null.
 const GestureRecognizerFactoryWithHandlers(this._constructor, this._initializer)

 final GestureRecognizerFactoryConstructor<T> _constructor;

 final GestureRecognizerFactoryInitializer<T> _initializer;

@override
 T constructor() => _constructor();

@override
 void initializer(T instance) => _initializer(instance); 
}

RawGestureDetector

/// A widget that detects gestures described by the given gesture
/// factories.
RawGestureDetector extends StatefulWidget {
  /// The gestures that this widget will attempt to recognize.
 ///
 /// This should be a map from [GestureRecognizer] subclasses to
 /// [GestureRecognizerFactory] subclasses specialized with the same type.

final Map<Type, GestureRecognizerFactory> gestures;

/// The widget below this widget in the tree.
 final Widget child;

@override
 RawGestureDetectorState createState() => RawGestureDetectorState();
}

RawGestureDetectorState

RawGestureDetectorState extends State<RawGestureDetector> {
  @override
 Widget build(BuildContext context) {
  Widget result = Listener(//main
   onPointerDown: _handlePointerDown,

  behavior: widget.behavior ?? _defaultBehavior,
   child: widget.child,
  );
  
  if (!widget.excludeFromSemantics)
   result = _GestureSemantics(
    child: result,
    assignSemantics: _updateSemanticsForRenderObject,
   );
  return result;
 }
  
}

_handlePointerDown

  void _handlePointerDown(PointerDownEvent event) {
    assert(_recognizers != null);
    for (GestureRecognizer recognizer in _recognizers.values)
        recognizer.addPointer(event);
    }

Listener

/// A widget that calls callbacks in response to common pointer events.

/// Rather than listening for raw pointer events, consider listening for
 /// higher-level gestures using [GestureDetector].

/// If it has a child, this widget defers to the child for sizing behavior. If
 /// it does not have a child, it grows to fit the parent instead.
Listener extends StatelessWidget {
  const Listener({
  Key key,
  this.onPointerDown,
  this.onPointerMove,

   this.onPointerUp,
  this.onPointerCancel,
  this.onPointerSignal,
  this.behavior = HitTestBehavior.deferToChild,
  Widget child,
 })

@override
 Widget build(BuildContext context) {
  Widget result = _child;

  result = _PointerListener(
  onPointerDown: onPointerDown,
  onPointerUp: onPointerUp,
  onPointerMove: onPointerMove,
  onPointerCancel: onPointerCancel,
  onPointerSignal: onPointerSignal,
  behavior: behavior,
  child: result,
 );
 return result;
}

_PointerListener

_PointerListener extends SingleChildRenderObjectWidget {
  final PointerDownEventListener onPointerDown;
 final PointerMoveEventListener onPointerMove;
 final PointerUpEventListener onPointerUp;
 final PointerCancelEventListener onPointerCancel;
 final PointerSignalEventListener onPointerSignal;
 final HitTestBehavior behavior;

@override
 RenderPointerListener createRenderObject(BuildContext context) {
  return RenderPointerListener(
   onPointerDown: onPointerDown,
   onPointerMove: onPointerMove,
   onPointerUp: onPointerUp,
   onPointerCancel: onPointerCancel,
   onPointerSignal: onPointerSignal,
   behavior: behavior,
  );
 }
}

RenderPointerListener.handleEvent

/// Calls callbacks in response to common pointer events.

/// It responds to events that can construct gestures, such as when the
 /// pointer is pressed, moved, then released or canceled.
RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
/// Called when a pointer comes into contact with the screen (for touch
 /// pointers), or has its button pressed (for mouse pointers) at this widget's
 /// location.
 PointerDownEventListener onPointerDown;

 /// Called when a pointer that triggered an [onPointerDown] changes position.
 PointerMoveEventListener onPointerMove;

 /// Called when a pointer that triggered an [onPointerDown] is no longer in
 /// contact with the screen.
 PointerUpEventListener onPointerUp;

 /// Called when the input from a pointer that triggered an [onPointerDown] is
 /// no longer directed towards this receiver.
 PointerCancelEventListener onPointerCancel;

 /// Called when a pointer signal occurs over this object.
 PointerSignalEventListener onPointerSignal;

@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);
 }

}

_handlepointerdown

GestureRecognizer

addPointer_event

/// Registers a new pointer that might be relevant to this gesture detector.
void addPointer(PointerDownEvent event) {
  _pointerToKind[event.pointer] = event.kind;
  if (isPointerAllowed(event)) {
   addAllowedPointer(event);//main
  } else {
   handleNonAllowedPointer(event);
  }
 }

/// Registers a new pointer that's been checked to be allowed by this gesture recognizer.
 ///
 /// Subclasses of [GestureRecognizer] are supposed to override this method
 /// instead of [addPointer] because [addPointer] will be called for each
 /// pointer being added while [addAllowedPointer] is only called for pointers
 /// that are allowed by this recognizer.
 @protected
 void addAllowedPointer(PointerDownEvent event) { }

PrimaryPointerGestureRecognizer.addAllowedPointer

//PrimaryPointerGestureRecognizer

@override
 void addAllowedPointer(PointerDownEvent event) {
  startTrackingPointer(event.pointer, event.transform);//main

  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));
  }
 }

OneSequenceGestureRecognizer.startTrackingPointer

/// Causes events related to the given pointer ID to be routed to this recognizer.
@protected
 void startTrackingPointer(int pointer, [Matrix4 transform]) {
 //事件传递的最后一站其实就是GestureBinding.handleEvent方法,到最后就是调用pointer.route方法路由事件,所以还要调用GestureRecognizer的handleEvent方法。

  GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent, transform);//main
  _trackedPointers.add(pointer);
  assert(!_entries.containsValue(pointer));
  _entries[pointer] = _addPointerToArena(pointer);//main
 }

/// Called when a pointer event is routed to this recognizer.
 @protected
 void handleEvent(PointerEvent event);

pointerRouter.addRoute

/// Adds a route to the routing table.
  ///
  /// Whenever this object routes a [PointerEvent] corresponding to
  /// pointer, call route.
  ///
  /// Routes added reentrantly within [PointerRouter.route] will take effect when
  /// routing the next event.
  void addRoute(int pointer, PointerRoute route, [Matrix4? transform]) {
    final Map<PointerRoute, Matrix4?> routes = _routeMap.putIfAbsent(
      pointer,
      () => <PointerRoute, Matrix4?>{},
    );
    assert(!routes.containsKey(route));
    routes[route] = transform;
  }

OneSequenceGestureRecognizer._addPointerToArena

GestureArenaEntry _addPointerToArena(int pointer) {
  if (_team != null)
   return _team.add(pointer, this);
  return GestureBinding.instance.gestureArena.add(pointer, this);//main
 }

GestureArenaManager

add

/// The first member to accept or the last member to not reject wins.
GestureArenaManager {
  //每组point操作(一次down->move->up)对应一个竞技场地_GestureArena,
  //每个竞技场地_GestureArena对应多个Recognizer同场竞技
  final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};
  
  /// Adds a new member (e.g., gesture recognizer) to the arena.
 GestureArenaEntry add(int pointer, GestureArenaMember member) {
  final _GestureArena state = _arenas.putIfAbsent(pointer, () {
   return _GestureArena();//main
  });
  state.add(member);
  return GestureArenaEntry._(this, pointer, member);//main
 }
}

参考

HitTestResult

/// The result of performing a hit test.
/// An unmodifiable list of [HitTestEntry] objects recorded during the hit test.
 ///
 /// The first entry in the path is the most specific, typically the one at
 /// the leaf of tree being hit tested. Event propagation starts with the most
 /// specific (i.e., first) entry and proceeds in order through the path.
 Iterable<HitTestEntry> get path => _path;
 final List<HitTestEntry> _path;

add

/// Add a [HitTestEntry] to the path.
 ///
 /// The new entry is added at the end of the path, which means entries should
 /// be added in order from most specific to least specific, typically during an
 /// upward walk of the tree being hit tested.
 void add(HitTestEntry entry) {
  _path.add(entry);
 }

HitTestEntry

/// Data collected during a hit test about a specific [HitTestTarget].
 ///
 /// Subclass this object to pass additional information from the hit test phase
 /// to the event propagation phase.

/// Creates a hit test entry.
 HitTestEntry(this.target);


/// The [HitTestTarget] encountered during the hit test.
 final HitTestTarget target;

HitTestTarget

/// An object that can handle events.
HitTestTarget {
  /// Override this method to receive events.
  void handleEvent(PointerEvent event, HitTestEntry entry);
}

PointerEvent.pointer

abstract class PointerEvent with Diagnosticable {
  /// Unique identifier for the pointer, not reused. Changes for each new
  /// pointer down event.
  final int pointer;
}

https://flutter.dev/docs/development/ui/advanced/gestures

十三、全面深入触摸和滑动原理· Flutter 完整开发实战详解系列

image

事实上并不是所有的控件的 RenderObject 子类都会处理 handleEvent ,大部分时候,只有带有 RenderPointerListener (RenderObject) / Listener (Widget) 的才会处理 handleEvent 事件,并且从上述源码可以看出,handleEvent 的执行是不会被拦截打断的。

Flutter中的事件流和手势简析

HitTestResult中的路径顺序一般就是:

目标节点–>父节点–>根节点–>GestureBinding

接着PointerDown,PointerMove,PointerUp,PointerCancel等事件分发,都根据这个顺序来遍历调用它们的handleEvent方法

Flutter触摸事件(1)