Flutter:为特定的 PointerDeviceKind 过滤 hitevent,只吸收这些

Flutter: Filter hitevent for specific PointerDeviceKind and only absorb these

我正在尝试使用“全局”侦听器读取手写笔,同时仍然能够用手指与 UI 的其余部分进行交互。传递给 Listener 小部件的侦听器的事件对象实际上有一个 属性 用于设备类型,但我无法告诉它哪些事件要吸收,哪些不吸收。您只能为每个带有 HitTestBehavior 的事件指定它,但这不是我想要的。

我试着对 Listener 小部件进行了一些逆向工程,但在您必须决定是否触发时似乎不可能知道指针设备的类型。而且我也找不到如何在 RenderObject 或类似的东西提供的 handleEvent 回调中取消事件。

Listener(
  onPointerDown: (event) {
    if (!pointerKinds.contains(event.kind)) return;
    // Absorb now
    ...
  },
);
class _SomeRenderObject extends RenderProxyBox {
  @override
  void handleEvent(PointerEvent event, covariant HitTestEntry entry) {
    if(event.kind != PointerDeviceKind.stylus) {
      // Cancel event
    }
  }
}

原来,我正在寻找的机制是建立在 Listener 上的,叫做 gesture-disambiguation。 它的 API 通过 RawGestureDetectorGestureRecognizer 公开。例如,这些在 GestureDetector 的幕后使用。 Listener本身其实很少用来监听事件的

A GestureRecognizer 需要确定某些用户交互是否适合特定手势,当它适合时,它可以声明它需要的任何指针,因此没有其他 GestureRecognizer 可以声明这些指针。

flutter 中已经有很多可用的实现,例如 DragGestureRecognizer,事实证明,它们已经可以过滤特定的 PointerDeviceKind。 Constructor 有一个 supportedDevices 属性 可以使用。但是由于某些原因,您不能直接在GestureDetector中使用它,而必须使用RawGestureDetector,其中您必须自己构造GestureRecognizer。这是一个例子:

Widget build(BuildContext context) {
  Map<Type, GestureRecognizerFactory> gestures = {
    DragGestureRecognizer:
        GestureRecognizerFactoryWithHandlers<DragGestureRecognizer>(
      () => DragGestureRecognizer(supportedDevices: {PointerDeviceKind.stylus})
        ..onStart = _onStart
        ..onUpdate = _onUpdate
        ..onEnd = _onEnd,
      (instance) => instance
        ..onStart = _onStart
        ..onUpdate = _onUpdate
        ..onEnd = _onEnd,
    ),
  };

  return RawGestureDetector(
    child: child,
    gestures: gestures,
  );
}

你涉及的样板文件有点多!

但我走得更远,因为我不想在指针移动后开始拖动手势,而是在指针触摸屏幕的那一刻开始,并实现了我自己的 GestureRecognizer(基于我在 DragGestureRecognizer 中的发现):

class InstantDragGestureRecognizer extends OneSequenceGestureRecognizer {
  GestureDragStartCallback? onStart;
  GestureDragUpdateCallback? onUpdate;
  GestureDragEndCallback? onEnd;

  Duration? _startTimestamp;

  late OffsetPair _initialPosition;

  int? _pointer;

  InstantDragGestureRecognizer({
    Object? debugOwner,
    Set<PointerDeviceKind>? supportedDevices,
  }) : super(supportedDevices: supportedDevices);

  @override
  String get debugDescription => "instant-drag";

  @override
  void didStopTrackingLastPointer(int pointer) {
    _pointer = null;
    _checkEnd(pointer);
  }

  // called for every event that involves a pointer, tracked by this recognizer
  @override
  void handleEvent(PointerEvent event) {
    _startTimestamp = event.timeStamp;
    if (event is PointerMoveEvent) {
      _checkUpdate(
        sourceTimeStamp: event.timeStamp,
        delta: event.localDelta,
        primaryDelta: null,
        globalPosition: event.position,
        localPosition: event.localPosition,
      );
    }
    if (event is PointerUpEvent || event is PointerCancelEvent) 
      stopTrackingPointer(event.pointer);
  }

  // new pointer touches the screen and needs to be registered for gesture 
  // tracking, override [isPointerAllowed] to define, which pointer is valid for 
  // this gesture
  @override
  void addAllowedPointer(PointerDownEvent event) {
    if (_pointer != null) return;
    super.addAllowedPointer(event);
    // claim tracked pointers
    resolve(GestureDisposition.accepted);
    _pointer = event.pointer;
    _initialPosition = OffsetPair(global: event.position, local: event.localPosition);
  }

  // called after pointer was claimed
  @override
  void acceptGesture(int pointer) {
    _checkStart(_startTimestamp!, pointer);
  }

  // copied from [DragGestureRecognizer]
  void _checkStart(Duration timestamp, int pointer) {
    if (onStart != null) {
      final DragStartDetails details = DragStartDetails(
        sourceTimeStamp: timestamp,
        globalPosition: _initialPosition.global,
        localPosition: _initialPosition.local,
        kind: getKindForPointer(pointer),
      );
      invokeCallback<void>('onStart', () => onStart!(details));
    }
  }

  // copied from [DragGestureRecognizer]
  void _checkUpdate({
    Duration? sourceTimeStamp,
    required Offset delta,
    double? primaryDelta,
    required Offset globalPosition,
    Offset? localPosition,
  }) {
    if (onUpdate != null) {
      final DragUpdateDetails details = DragUpdateDetails(
        sourceTimeStamp: sourceTimeStamp,
        delta: delta,
        primaryDelta: primaryDelta,
        globalPosition: globalPosition,
        localPosition: localPosition,
      );
      invokeCallback<void>('onUpdate', () => onUpdate!(details));
    }
  }

  // copied from [DragGestureRecognizer]
  void _checkEnd(int pointer) {
    if (onEnd != null) {
      invokeCallback<void>('onEnd', () => onEnd!(DragEndDetails(primaryVelocity: 0.0)));
    }
  }
}

这实际上是一种非常好的实现手势消歧的方法!使用 flutter 的好处之一,但如果有更多关于如何编写手势识别器的文档就更好了!