Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] Modify 300ms delay when using double tap #106170

Open
toly1994328 opened this issue Jun 17, 2022 · 16 comments
Open

[Proposal] Modify 300ms delay when using double tap #106170

toly1994328 opened this issue Jun 17, 2022 · 16 comments
Labels
c: new feature Nothing broken; request for a new capability c: proposal A detailed proposal for a change to Flutter f: gestures flutter/packages/flutter/gestures repository. framework flutter/packages/flutter repository. See also f: labels. P3 Issues that are less important to the Flutter project team-framework Owned by Framework team triaged-framework Triaged by Framework team

Comments

@toly1994328
Copy link

When I use DoubleTap and Tap together ,tap will delay 300ms to check it's DoubleTap or not . It doesn't matter on mobile,but in desktop , there is a significant delay when tap fast . I know the reason is kDoubleTapTimeout ms Timer,but it's const. May I have access to change it , or have any solution to handle click speed ?

6311655446402_ pic

6321655446402_ pic


flutter --version
Flutter 3.0.1 • channel stable • https://github.com/flutter/flutter.git
Framework • revision fb57da5 (4 weeks ago) • 2022-05-19 15:50:29 -0700
Engine • revision caaafc5604
Tools • Dart 2.17.1 • DevTools 2.12.2

@maheshmnj maheshmnj added the in triage Presently being triaged by the triage team label Jun 17, 2022
@maheshmnj
Copy link
Member

maheshmnj commented Jun 17, 2022

Hi @toly1994328, Thanks for filing the issue. This is currently not possible to configure the double-tap duration. I will leave this for the team to decide.

void _startDoubleTapTimer() {

@maheshmnj maheshmnj added c: new feature Nothing broken; request for a new capability framework flutter/packages/flutter repository. See also f: labels. f: gestures flutter/packages/flutter/gestures repository. c: proposal A detailed proposal for a change to Flutter and removed in triage Presently being triaged by the triage team labels Jun 17, 2022
@maheshmnj maheshmnj changed the title [desktop] : How to change 300ms delay when use double tap and single tap [Proposal] Modify 300ms delay when using double tap Jun 17, 2022
@jonahwilliams
Copy link
Member

Are you trying to use a double tap gesture recognizer to simulate a double click recognizer?

@animator
Copy link

animator commented Apr 19, 2023

Hi Flutter team, Any updates on this issue?
For those who are not aware. The issue was first reported in #22950 (2018). The issue was closed and a very poor solution was suggested which required modifying flutter framework code.

@kozw
Copy link

kozw commented May 7, 2023

As a workaround you can wrap a GestureDetector with a Listener and use the onPointerDown as an alternative to the delayed onTap.

Listener(
  onPointerDown: (event) => handleTap(),
  child: GestureDetector(
    onDoubleTap: handleDoubleTap,
    child: ...));

Use with a caution; comes with the side-effects where both a double tap and two taps are registered, but at least there is no delay :)

@animator
Copy link

animator commented May 7, 2023

@kozw I don't understand why this issue and various workarounds still exist.
All @toly1994328 , me and other devs have been asking for a long time is that the Flutter team should provide us a simple way to modify kDoubleTapTimeout. 300ms might be good for mobiles, but for desktop it makes a lot of difference and severely deters user experience.

@kozw
Copy link

kozw commented May 7, 2023

I'm with you, should be an easy fix

@juanmasilf
Copy link

juanmasilf commented May 17, 2023

I leave a workaround in case it helps someone:

You can extend from TapGestureRecognizer to declare victory in a shorter period of time by adding your own logic to the handleEvent method

/// A custom tap gesture recognizer that accepts the gesture
/// after 150 milliseconds instead of 300 milliseconds.
/// This is done to make the gesture detector more responsive
/// than the default one
class _ResponsiveTapGestureRecognizer extends TapGestureRecognizer {
  _ResponsiveTapGestureRecognizer({
    super.debugOwner,
  });

  /// Timer to keep track of the last tap up event
  Timer? _lastTapUpEvent;

  @override
  void handleEvent(PointerEvent event) {
    if (event is PointerDownEvent) {
      _lastTapUpEvent?.cancel();
      _lastTapUpEvent = null;
    }
    if (event is PointerUpEvent) {
      _lastTapUpEvent = Timer(
        Duration(milliseconds: 150),
        () {
          resolve(GestureDisposition.accepted);
          acceptGesture(event.pointer);
        },
      );
    }
    super.handleEvent(event);
  }
}

You could use this GestureRecognizer in a RawGestureDetector to have this behavior

@jpenna
Copy link

jpenna commented Feb 5, 2024

Also, besides allowing programmers to customize this value, it should be decreased for Desktop platforms by default, because it leads to a very poor user experience.

@jpenna
Copy link

jpenna commented Feb 5, 2024

GestureRecognizer

@juanmasilf Could you provide an example of how to use it in RawGestureDetector?

@tomekit
Copy link

tomekit commented Feb 27, 2024

I believe this shall be solved on a Flutter level, in my case this code works:

int lastTap = DateTime.now().millisecondsSinceEpoch;
    int consecutiveTaps = 1;
    return GestureDetector(
        onTap: () {
          int now = DateTime.now().millisecondsSinceEpoch;
          if (now - lastTap < 300) {
            consecutiveTaps++;
            if (consecutiveTaps >= 2) {
              onDoubleTapFn();
            }
          } else {
            consecutiveTaps = 1;
            onTapFn();
          }
          lastTap = now;
        },
        child: widget
    );

Bear in mind that onTapFn handler will be called first and then onDoubleTapFn handler will be called second.
That's the idea, instead of blocking UI for 300ms in order to determine whether to call onTapFn or wait for onDoubleTapFn to call, these two functions are called and it's up to you to prepare your code to deal with the onTapFn call side effects.

@vizakenjack
Copy link

I believe this shall be solved on a Flutter level, in my case this code works:

int lastTap = DateTime.now().millisecondsSinceEpoch;
    int consecutiveTaps = 1;
    return GestureDetector(
        onTap: () {
          int now = DateTime.now().millisecondsSinceEpoch;
          if (now - lastTap < 300) {
            consecutiveTaps++;
            if (consecutiveTaps >= 2) {
              onDoubleTapFn();
            }
          } else {
            consecutiveTaps = 1;
            onTapFn();
          }
          lastTap = now;
        },
        child: widget
    );

Bear in mind that onTapFn handler will be called first and then onDoubleTapFn handler will be called second. That's the idea, instead of blocking UI for 300ms in order to determine whether to call onTapFn or wait for onDoubleTapFn to call, these two functions are called and it's up to you to prepare your code to deal with the onTapFn call side effects.

Fantastic idea, I love it.

I had a lot of issues with nested GestureDetectors.
In my case, I never had a single widget with both onTap and onDoubleTap.

Usually, its one parent widget with onDoubleTap, for example, a MessageBubble container.
And child widget with onTap, for example, a list of links in a message.

Now I'm finally able to have both events: onTap for links and onDoubleTap for parent widget, without any delay.

Thanks!

@margorczynski
Copy link

margorczynski commented Mar 24, 2024

Hey, any progress on this in the last two years?

As for @tomekit solution it is unfortunately wrong - without a timer you can't separate a single tap from a double, each time you do a double it'll also execute the one tap. A working solution:

Timer? tapTimer;
  bool isTapped = false;

 GestureDetector(
        onTap: () {
          if (isTapped) {
            tapTimer?.cancel();
            isTapped = false;
            log("New item double tap");
          } else {
            log("Single tap");
            isTapped = true;
            tapTimer = Timer(Duration(milliseconds: 300), () {
              log("Single tap execute");
              isTapped = false;
            });
          }
        }
  )
}

@vizakenjack
Copy link

@margorczynski your solution is the same as GestureDetector with onTap and onDoubleTap
The only difference is that you can specify a timeout

@margorczynski
Copy link

@vizakenjack well you simply modify the Duration value to modify the time window between two single taps and a double tap. The gesture detector doesn't offer that from what I understand as it is hardcoded to 300ms - this is the topic of this issue.

@vizakenjack
Copy link

@margorczynski actually there is another issue.

For example, you have a Message widget with onDoubleTap to reply.

And you have a Link widget as child in Message, with onTap.

And when you tap on that child link, there will be an extra delay because of parent widget.

@HayesBarber
Copy link

@margorczynski and @vizakenjack and others, here is an implementation to separate tap counts, and also configure delay. This should support N taps, and would only call its respective callback:

class MultiTappable extends StatefulWidget {
  final Map<int, VoidCallback> onNTap;
  final int tapThresholdMilliseconds;
  final bool haptic;
  final Widget child;

  const MultiTappable({
    super.key,
    required this.onNTap,
    this.tapThresholdMilliseconds = 200,
    this.haptic = true,
    required this.child,
  });

  @override
  State<MultiTappable> createState() => _MultiTappableState();
}

class _MultiTappableState extends State<MultiTappable> {
  int _taps = 0;
  Timer? _t;

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: () {
        _taps++;
        _t?.cancel();
        _t = Timer(widget.tapThresholdMilliseconds.milliseconds, () {
          if (widget.haptic) HapticFeedback.lightImpact();
          widget.onNTap[_taps]?.call();
          _taps = 0;
        });
      },
      child: widget.child,
    );
  }
}

To use this, it would look something like this:

return MultiTappable(
      onNTap: {
        1: () => print('one'),
        2: () => print('two'),
        //etc...
      },
      child: child,
    );

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: new feature Nothing broken; request for a new capability c: proposal A detailed proposal for a change to Flutter f: gestures flutter/packages/flutter/gestures repository. framework flutter/packages/flutter repository. See also f: labels. P3 Issues that are less important to the Flutter project team-framework Owned by Framework team triaged-framework Triaged by Framework team
Projects
None yet
Development

No branches or pull requests