在Flutter中检测鼠标右键或鼠标中键双击



Flutter已经包含

  • 用于鼠标右键单击检测的CCD_ 1属性
  • 鼠标中键单击检测的onTertiaryTap属性,以及
  • onDoubleTap属性用于鼠标左键双击检测

Flutter是否也包含鼠标右键或鼠标中键双击检测的属性?

Flutter中内置的手势检测器不包含onDoubleSecondary tap或onDoubleTertiaryTap的属性,但您可以创建自己的手势识别器,并将其添加到RawGestureDetector中。

这里有一个手势识别器,可以识别双击:

class _CountdownZoned {
_CountdownZoned({ required Duration duration })
: assert(duration != null) {
Timer(duration, _onTimeout);
}
bool _timeout = false;
bool get timeout => _timeout;
void _onTimeout() {
_timeout = true;
}
}
class _TapTracker {
_TapTracker({
required PointerDownEvent event,
required this.entry,
required Duration doubleTapMinTime,
required this.gestureSettings,
}) : assert(doubleTapMinTime != null),
assert(event != null),
assert(event.buttons != null),
pointer = event.pointer,
_initialGlobalPosition = event.position,
initialButtons = event.buttons,
_doubleTapMinTimeCountdown = _CountdownZoned(duration: doubleTapMinTime);
final DeviceGestureSettings? gestureSettings;
final int pointer;
final GestureArenaEntry entry;
final Offset _initialGlobalPosition;
final int initialButtons;
final _CountdownZoned _doubleTapMinTimeCountdown;
bool _isTrackingPointer = false;
void startTrackingPointer(PointerRoute route, Matrix4? transform) {
if (!_isTrackingPointer) {
_isTrackingPointer = true;
GestureBinding.instance.pointerRouter.addRoute(pointer, route, transform);
}
}
void stopTrackingPointer(PointerRoute route) {
if (_isTrackingPointer) {
_isTrackingPointer = false;
GestureBinding.instance.pointerRouter.removeRoute(pointer, route);
}
}
bool isWithinGlobalTolerance(PointerEvent event, double tolerance) {
final Offset offset = event.position - _initialGlobalPosition;
return offset.distance <= tolerance;
}
bool hasElapsedMinTime() {
return _doubleTapMinTimeCountdown.timeout;
}
bool hasSameButton(PointerDownEvent event) {
return event.buttons == initialButtons;
}
}
class DoubleRightClick extends GestureRecognizer {
DoubleRightClick({
super.debugOwner,
@Deprecated(
'Migrate to supportedDevices. '
'This feature was deprecated after v2.3.0-1.0.pre.',
)
super.kind,
super.supportedDevices,
});
GestureTapDownCallback? onDoubleTapDown;
GestureDoubleTapCallback? onDoubleTap;
GestureTapCancelCallback? onDoubleTapCancel;
Timer? _doubleTapTimer;
_TapTracker? _firstTap;
final Map<int, _TapTracker> _trackers = <int, _TapTracker>{};
@override
bool isPointerAllowed(PointerDownEvent event) {
if (_firstTap == null) {
switch (event.buttons) {
case kSecondaryMouseButton:
if (onDoubleTapDown == null &&
onDoubleTap == null &&
onDoubleTapCancel == null) {
return false;
}
break;
default:
return false;
}
}
return super.isPointerAllowed(event);
}
@override
void addAllowedPointer(PointerDownEvent event) {
if (_firstTap != null) {
if (!_firstTap!.isWithinGlobalTolerance(event, kDoubleTapSlop)) {
// Ignore out-of-bounds second taps.
return;
} else if (!_firstTap!.hasElapsedMinTime() || !_firstTap!.hasSameButton(event)) {
// Restart when the second tap is too close to the first (touch screens
// often detect touches intermittently), or when buttons mismatch.
_reset();
return _trackTap(event);
} else if (onDoubleTapDown != null) {
final TapDownDetails details = TapDownDetails(
globalPosition: event.position,
localPosition: event.localPosition,
kind: getKindForPointer(event.pointer),
);
invokeCallback<void>('onDoubleTapDown', () => onDoubleTapDown!(details));
}
}
_trackTap(event);
}
void _trackTap(PointerDownEvent event) {
_stopDoubleTapTimer();
final _TapTracker tracker = _TapTracker(
event: event,
entry: GestureBinding.instance.gestureArena.add(event.pointer, this),
doubleTapMinTime: kDoubleTapMinTime,
gestureSettings: gestureSettings,
);
_trackers[event.pointer] = tracker;
tracker.startTrackingPointer(_handleEvent, event.transform);
}
void _handleEvent(PointerEvent event) {
final _TapTracker tracker = _trackers[event.pointer]!;
if (event is PointerUpEvent) {
if (_firstTap == null) {
_registerFirstTap(tracker);
} else {
_registerSecondTap(tracker);
}
} else if (event is PointerMoveEvent) {
if (!tracker.isWithinGlobalTolerance(event, kDoubleTapTouchSlop)) {
_reject(tracker);
}
} else if (event is PointerCancelEvent) {
_reject(tracker);
}
}
@override
void acceptGesture(int pointer) { }
@override
void rejectGesture(int pointer) {
_TapTracker? tracker = _trackers[pointer];
// If tracker isn't in the list, check if this is the first tap tracker
if (tracker == null &&
_firstTap != null &&
_firstTap!.pointer == pointer) {
tracker = _firstTap;
}
// If tracker is still null, we rejected ourselves already
if (tracker != null) {
_reject(tracker);
}
}
void _reject(_TapTracker tracker) {
_trackers.remove(tracker.pointer);
tracker.entry.resolve(GestureDisposition.rejected);
_freezeTracker(tracker);
if (_firstTap != null) {
if (tracker == _firstTap) {
_reset();
} else {
_checkCancel();
if (_trackers.isEmpty) {
_reset();
}
}
}
}
@override
void dispose() {
_reset();
super.dispose();
}
void _reset() {
_stopDoubleTapTimer();
if (_firstTap != null) {
if (_trackers.isNotEmpty) {
_checkCancel();
}
// Note, order is important below in order for the resolve -> reject logic
// to work properly.
final _TapTracker tracker = _firstTap!;
_firstTap = null;
_reject(tracker);
GestureBinding.instance.gestureArena.release(tracker.pointer);
}
_clearTrackers();
}
void _registerFirstTap(_TapTracker tracker) {
_startDoubleTapTimer();
GestureBinding.instance.gestureArena.hold(tracker.pointer);
// Note, order is important below in order for the clear -> reject logic to
// work properly.
_freezeTracker(tracker);
_trackers.remove(tracker.pointer);
_clearTrackers();
_firstTap = tracker;
}
void _registerSecondTap(_TapTracker tracker) {
_firstTap!.entry.resolve(GestureDisposition.accepted);
tracker.entry.resolve(GestureDisposition.accepted);
_freezeTracker(tracker);
_trackers.remove(tracker.pointer);
_checkUp(tracker.initialButtons);
_reset();
}
void _clearTrackers() {
_trackers.values.toList().forEach(_reject);
assert(_trackers.isEmpty);
}
void _freezeTracker(_TapTracker tracker) {
tracker.stopTrackingPointer(_handleEvent);
}
void _startDoubleTapTimer() {
_doubleTapTimer ??= Timer(kDoubleTapTimeout, _reset);
}
void _stopDoubleTapTimer() {
if (_doubleTapTimer != null) {
_doubleTapTimer!.cancel();
_doubleTapTimer = null;
}
}
void _checkUp(int buttons) {
assert(buttons == kSecondaryMouseButton);
if (onDoubleTap != null) {
invokeCallback<void>('onDoubleTap', onDoubleTap!);
}
}
void _checkCancel() {
if (onDoubleTapCancel != null) {
invokeCallback<void>('onDoubleTapCancel', onDoubleTapCancel!);
}
}
@override
String get debugDescription => 'double tap';
}

你这样使用它:

RawGestureDetector(
gestures: {
DoubleRightClick: GestureRecognizerFactoryWithHandlers<DoubleRightClick>(
() => DoubleRightClick(),
(DoubleRightClick instance) {
instance.onDoubleTap = () {
debugPrint('Double Right Click');
};
},
),
},
child: Container(
width: 200,
height: 200,
color: Colors.red,
child: SizedBox(
width: 200,
height: 200,
child: FlutterLogo(),
),
),
),

这段来自Flutter团队的YouTube视频解释了手势竞技场的工作原理。

最新更新