I've been trying to build a tap detector that can detect both double and tripe tap. After my efforts failed I searched a long time on the net to find something ready to use but no luck! It's strange that libraries for something like this are so scarce. Any help ??
-
HAve you seen this? http://stackoverflow.com/questions/15861638/identify-triple-tap-on-custom-view – Mou Jan 03 '15 at 16:55
-
yes I have..in this example if the view is touched a tap is registered no matter what. a tap should be only registered if it's duration is
– Jan 03 '15 at 17:42
4 Answers
You can try something like this.
Though I would generally recommend against using triple taps as a pattern as it is not something users are generally used to, so unless it's properly communicated to them, most might never know they can triple tap a view. Same goes for double taping actually on mobile devices, it's not always an intuitive way to interact in that environment.
view.setOnTouchListener(new View.OnTouchListener() {
Handler handler = new Handler();
int numberOfTaps = 0;
long lastTapTimeMs = 0;
long touchDownMs = 0;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchDownMs = System.currentTimeMillis();
break;
case MotionEvent.ACTION_UP:
handler.removeCallbacksAndMessages(null);
if ((System.currentTimeMillis() - touchDownMs) > ViewConfiguration.getTapTimeout()) {
//it was not a tap
numberOfTaps = 0;
lastTapTimeMs = 0;
break;
}
if (numberOfTaps > 0
&& (System.currentTimeMillis() - lastTapTimeMs) < ViewConfiguration.getDoubleTapTimeout()) {
numberOfTaps += 1;
} else {
numberOfTaps = 1;
}
lastTapTimeMs = System.currentTimeMillis();
if (numberOfTaps == 3) {
Toast.makeText(getApplicationContext(), "triple", Toast.LENGTH_SHORT).show();
//handle triple tap
} else if (numberOfTaps == 2) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
//handle double tap
Toast.makeText(getApplicationContext(), "double", Toast.LENGTH_SHORT).show();
}
}, ViewConfiguration.getDoubleTapTimeout());
}
}
return true;
}
});

- 521
- 4
- 5
-
Using this checkstyle plugin suggests that: onTouch should call View#performClick when a click is detected – zygimantus Feb 02 '17 at 21:07
-
Actually you need to use this line after `case MotionEvent.ACTION_UP`: `v.performClick();` – zygimantus Apr 27 '17 at 19:05
-
You should really only call currentTimeMillis() ONCE at the beginning of your method, set it to a variable, and use it throughout. Calling it multiple times may result in slightly different values each time time you use it and lead to subtle bugs – Michael Peterson Aug 14 '17 at 13:34
-
This was helpful, but note that `getTapTimeout()` is not intended as a maximum duration for a tap, but rather "the duration in milliseconds we will wait to see if a touch event is a tap or a scroll. If the user does not move within this interval, it is considered to be a tap." (quoted from https://developer.android.com/reference/android/view/ViewConfiguration.html#getTapTimeout() ). I found that the code above made it difficult to reliably detect taps because `getTapTimeout()` is too small to use as maximum tap duration. Using `getLongPressTimeout()` works much better for me. – James Jul 24 '18 at 00:16
Here is a Kotlin implementation that can detect an arbitrary number of taps, and respects the various timeout and slop parameters found in the ViewConfiguration class. I have tried to minimise heap allocations in the event handlers.
import android.os.Handler
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import kotlin.math.abs
/*
* Detects an arbitrary number of taps in rapid succession
*
* The passed callback will be called for each tap, with two parameters:
* - the number of taps detected in rapid succession so far
* - a boolean flag indicating whether this is last tap of the sequence
*/
class MultiTapDetector(view: View, callback: (Int, Boolean) -> Unit) {
private var numberOfTaps = 0
private val handler = Handler()
private val doubleTapTimeout = ViewConfiguration.getDoubleTapTimeout().toLong()
private val tapTimeout = ViewConfiguration.getTapTimeout().toLong()
private val longPressTimeout = ViewConfiguration.getLongPressTimeout().toLong()
private val viewConfig = ViewConfiguration.get(view.context)
private var downEvent = Event()
private var lastTapUpEvent = Event()
data class Event(var time: Long = 0, var x: Float = 0f, var y: Float = 0f) {
fun copyFrom(motionEvent: MotionEvent) {
time = motionEvent.eventTime
x = motionEvent.x
y = motionEvent.y
}
fun clear() {
time = 0
}
}
init {
view.setOnTouchListener { v, event ->
when(event.action) {
MotionEvent.ACTION_DOWN -> {
if(event.pointerCount == 1) {
downEvent.copyFrom(event)
} else {
downEvent.clear()
}
}
MotionEvent.ACTION_MOVE -> {
// If a move greater than the allowed slop happens before timeout, then this is a scroll and not a tap
if(event.eventTime - event.downTime < tapTimeout
&& abs(event.x - downEvent.x) > viewConfig.scaledTouchSlop
&& abs(event.y - downEvent.y) > viewConfig.scaledTouchSlop) {
downEvent.clear()
}
}
MotionEvent.ACTION_UP -> {
val downEvent = this.downEvent
val lastTapUpEvent = this.lastTapUpEvent
if(downEvent.time > 0 && event.eventTime - event.downTime < longPressTimeout) {
// We have a tap
if(lastTapUpEvent.time > 0
&& event.eventTime - lastTapUpEvent.time < doubleTapTimeout
&& abs(event.x - lastTapUpEvent.x) < viewConfig.scaledDoubleTapSlop
&& abs(event.y - lastTapUpEvent.y) < viewConfig.scaledDoubleTapSlop) {
// Double tap
numberOfTaps++
} else {
numberOfTaps = 1
}
this.lastTapUpEvent.copyFrom(event)
// Send event
val taps = numberOfTaps
handler.postDelayed({
// When this callback runs, we know if it is the final tap of a sequence
// if the number of taps has not changed
callback(taps, taps == numberOfTaps)
}, doubleTapTimeout)
}
}
}
true
}
}
}

- 3,597
- 2
- 39
- 38
I developed an advanced version of the Iorgu solition that suits better my needs:
public abstract class OnTouchMultipleTapListener implements View.OnTouchListener {
Handler handler = new Handler();
private boolean manageInActionDown;
private float tapTimeoutMultiplier;
private int numberOfTaps = 0;
private long lastTapTimeMs = 0;
private long touchDownMs = 0;
public OnTouchMultipleTapListener() {
this(false, 1);
}
public OnTouchMultipleTapListener(boolean manageInActionDown, float tapTimeoutMultiplier) {
this.manageInActionDown = manageInActionDown;
this.tapTimeoutMultiplier = tapTimeoutMultiplier;
}
/**
*
* @param e
* @param numberOfTaps
*/
public abstract void onMultipleTapEvent(MotionEvent e, int numberOfTaps);
@Override
public final boolean onTouch(View v, final MotionEvent event) {
if (manageInActionDown) {
onTouchDownManagement(v, event);
} else {
onTouchUpManagement(v, event);
}
return true;
}
private void onTouchDownManagement(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchDownMs = System.currentTimeMillis();
handler.removeCallbacksAndMessages(null);
if (numberOfTaps > 0 && (System.currentTimeMillis() - lastTapTimeMs) < ViewConfiguration.getTapTimeout() * tapTimeoutMultiplier) {
numberOfTaps += 1;
} else {
numberOfTaps = 1;
}
lastTapTimeMs = System.currentTimeMillis();
if (numberOfTaps > 0) {
final MotionEvent finalMotionEvent = MotionEvent.obtain(event); // to avoid side effects
handler.postDelayed(new Runnable() {
@Override
public void run() {
onMultipleTapEvent(finalMotionEvent, numberOfTaps);
}
}, (long) (ViewConfiguration.getDoubleTapTimeout() * tapTimeoutMultiplier));
}
break;
case MotionEvent.ACTION_UP:
break;
}
}
private void onTouchUpManagement(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchDownMs = System.currentTimeMillis();
break;
case MotionEvent.ACTION_UP:
handler.removeCallbacksAndMessages(null);
if ((System.currentTimeMillis() - touchDownMs) > ViewConfiguration.getTapTimeout()) {
numberOfTaps = 0;
lastTapTimeMs = 0;
break;
}
if (numberOfTaps > 0 && (System.currentTimeMillis() - lastTapTimeMs) < ViewConfiguration.getDoubleTapTimeout()) {
numberOfTaps += 1;
} else {
numberOfTaps = 1;
}
lastTapTimeMs = System.currentTimeMillis();
if (numberOfTaps > 0) {
final MotionEvent finalMotionEvent = MotionEvent.obtain(event); // to avoid side effects
handler.postDelayed(new Runnable() {
@Override
public void run() {
onMultipleTapEvent(finalMotionEvent, numberOfTaps);
}
}, ViewConfiguration.getDoubleTapTimeout());
}
}
}
}

- 2,220
- 1
- 16
- 11
Use the view listener to detect first tap on the view object,then see how to manage twice back pressed to exit an activity on stackoverflow.com (use a handler post delay).
-
-
its called logic, that's what he was relating you to, and he was also trying to help @user2484359 .. this would have been a comment if he had enough reps – Elltz Jan 03 '15 at 18:41