gesture_detector.dart 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import 'dart:async';
  2. import 'package:flutter/gestures.dart';
  3. import 'package:flutter/widgets.dart';
  4. class TerminalGestureDetector extends StatefulWidget {
  5. const TerminalGestureDetector({
  6. super.key,
  7. this.child,
  8. this.onSingleTapUp,
  9. this.onTapUp,
  10. this.onTapDown,
  11. this.onSecondaryTapDown,
  12. this.onSecondaryTapUp,
  13. this.onTertiaryTapDown,
  14. this.onTertiaryTapUp,
  15. this.onLongPressStart,
  16. this.onLongPressMoveUpdate,
  17. this.onLongPressUp,
  18. this.onDragStart,
  19. this.onDragUpdate,
  20. this.onDoubleTapDown,
  21. });
  22. final Widget? child;
  23. final GestureTapUpCallback? onTapUp;
  24. final GestureTapUpCallback? onSingleTapUp;
  25. final GestureTapDownCallback? onTapDown;
  26. final GestureTapDownCallback? onSecondaryTapDown;
  27. final GestureTapUpCallback? onSecondaryTapUp;
  28. final GestureTapDownCallback? onDoubleTapDown;
  29. final GestureTapDownCallback? onTertiaryTapDown;
  30. final GestureTapUpCallback? onTertiaryTapUp;
  31. final GestureLongPressStartCallback? onLongPressStart;
  32. final GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate;
  33. final GestureLongPressUpCallback? onLongPressUp;
  34. final GestureDragStartCallback? onDragStart;
  35. final GestureDragUpdateCallback? onDragUpdate;
  36. @override
  37. State<TerminalGestureDetector> createState() =>
  38. _TerminalGestureDetectorState();
  39. }
  40. class _TerminalGestureDetectorState extends State<TerminalGestureDetector> {
  41. Timer? _doubleTapTimer;
  42. Offset? _lastTapOffset;
  43. // True if a second tap down of a double tap is detected. Used to discard
  44. // subsequent tap up / tap hold of the same tap.
  45. bool _isDoubleTap = false;
  46. // The down handler is force-run on success of a single tap and optimistically
  47. // run before a long press success.
  48. void _handleTapDown(TapDownDetails details) {
  49. widget.onTapDown?.call(details);
  50. if (_doubleTapTimer != null &&
  51. _isWithinDoubleTapTolerance(details.globalPosition)) {
  52. // If there was already a previous tap, the second down hold/tap is a
  53. // double tap down.
  54. widget.onDoubleTapDown?.call(details);
  55. _doubleTapTimer!.cancel();
  56. _doubleTapTimeout();
  57. _isDoubleTap = true;
  58. }
  59. }
  60. void _handleTapUp(TapUpDetails details) {
  61. if (!_isDoubleTap) {
  62. widget.onSingleTapUp?.call(details);
  63. _lastTapOffset = details.globalPosition;
  64. _doubleTapTimer = Timer(kDoubleTapTimeout, _doubleTapTimeout);
  65. }
  66. _isDoubleTap = false;
  67. }
  68. void _doubleTapTimeout() {
  69. _doubleTapTimer = null;
  70. _lastTapOffset = null;
  71. }
  72. bool _isWithinDoubleTapTolerance(Offset secondTapOffset) {
  73. if (_lastTapOffset == null) {
  74. return false;
  75. }
  76. final Offset difference = secondTapOffset - _lastTapOffset!;
  77. return difference.distance <= kDoubleTapSlop;
  78. }
  79. @override
  80. Widget build(BuildContext context) {
  81. final gestures = <Type, GestureRecognizerFactory>{};
  82. gestures[TapGestureRecognizer] =
  83. GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
  84. () => TapGestureRecognizer(debugOwner: this),
  85. (TapGestureRecognizer instance) {
  86. instance
  87. ..onTapDown = _handleTapDown
  88. ..onTapUp = _handleTapUp
  89. ..onSecondaryTapDown = widget.onSecondaryTapDown
  90. ..onSecondaryTapUp = widget.onSecondaryTapUp
  91. ..onTertiaryTapDown = widget.onTertiaryTapDown
  92. ..onTertiaryTapUp = widget.onTertiaryTapUp;
  93. },
  94. );
  95. gestures[LongPressGestureRecognizer] =
  96. GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
  97. () => LongPressGestureRecognizer(
  98. debugOwner: this,
  99. supportedDevices: {
  100. PointerDeviceKind.touch,
  101. // PointerDeviceKind.mouse, // for debugging purposes only
  102. },
  103. ),
  104. (LongPressGestureRecognizer instance) {
  105. instance
  106. ..onLongPressStart = widget.onLongPressStart
  107. ..onLongPressMoveUpdate = widget.onLongPressMoveUpdate
  108. ..onLongPressUp = widget.onLongPressUp;
  109. },
  110. );
  111. gestures[PanGestureRecognizer] =
  112. GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
  113. () => PanGestureRecognizer(
  114. debugOwner: this,
  115. supportedDevices: <PointerDeviceKind>{PointerDeviceKind.mouse},
  116. ),
  117. (PanGestureRecognizer instance) {
  118. instance
  119. ..dragStartBehavior = DragStartBehavior.down
  120. ..onStart = widget.onDragStart
  121. ..onUpdate = widget.onDragUpdate;
  122. },
  123. );
  124. return RawGestureDetector(
  125. gestures: gestures,
  126. excludeFromSemantics: true,
  127. child: widget.child,
  128. );
  129. }
  130. }