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 通过 RawGestureDetector
和 GestureRecognizer
公开。例如,这些在 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 的好处之一,但如果有更多关于如何编写手势识别器的文档就更好了!
我正在尝试使用“全局”侦听器读取手写笔,同时仍然能够用手指与 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 通过 RawGestureDetector
和 GestureRecognizer
公开。例如,这些在 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 的好处之一,但如果有更多关于如何编写手势识别器的文档就更好了!