I would suggest that you switch to the SimpleGestureListener and use the onDoubleTap() and onSingleTapConfirmed() methods.
Android distinguish between tap and double tap
-
30-05-2022 - |
Question
I am using the onTouch
method to catch a touch with ACTION_UP
and GestureDetector
to capture the double tap, my issue is a double tap results in a tap then a double tap, then a tap. Is there a way to have a double tap block a tap or something like that? I know logically what its doing is correct, so if you advise I find another way just comment, please dont down vote. Thanks!
Solution
OTHER TIPS
To be more precise than what is said by britzl, the GestureDetector
does the actual work of determining when something is a single tap, double tap, long press, etc. The SimpleGestureListener
is just a "listener" the GestureDetector
uses to indicate what it recognized. It implements OnGestureListener
and OnDoubleTapListener
just to always return false. Check out a snippet from the onTouchEvent(MotionEvent)
in GestureDetector
:
case MotionEvent.ACTION_DOWN:
if (mDoubleTapListener != null) {
boolean hadTapMessage = mHandler.hasMessages(TAP);
if (hadTapMessage) mHandler.removeMessages(TAP);
if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage &&
isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
// This is a second tap
mIsDoubleTapping = true;
// Give a callback with the first tap of the double-tap
handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
// Give a callback with down event of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else {
// This is a first tap
mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
}
}
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
if (mCurrentDownEvent != null) {
mCurrentDownEvent.recycle();
}
mCurrentDownEvent = MotionEvent.obtain(ev);
mAlwaysInTapRegion = true;
mAlwaysInBiggerTapRegion = true;
mStillDown = true;
mInLongPress = false;
mDeferConfirmSingleTap = false;
if (mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
+ TAP_TIMEOUT + LONGPRESS_TIMEOUT);
}
mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
handled |= mListener.onDown(ev);
break;
Then the desired result can be obtained by creating a GestureDetector
with the appropriate listener:
final View.OnTouchListener touch_listener = new View.OnTouchListener() {
@Override public boolean onTouch(View view, MotionEvent event) {
return _gesture_detector.onTouchEvent(event);
}
private final GestureDetector _gesture_detector = new GestureDetector(getContext()
, new GestureDetector.SimpleOnGestureListener() {
@Override public boolean onSingleTapConfirmed(MotionEvent event) {
// TODO: implement single tap behavior
// NOTE: returning true indicates that the gesture was handled
return true;
}
@Override public boolean onDoubleTap(MotionEvent event) {
// TODO: implement double tap behavior
// NOTE: returning true indicates that the gesture was handled
return true;
}
});
};
And from there, this OnTouchListener
can be set to the View
that wants the behavior.
It works by using the default GestureHandler
(which is a Handler
):
private class GestureHandler extends Handler {
GestureHandler() {
super();
}
GestureHandler(Handler handler) {
super(handler.getLooper());
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_PRESS:
mListener.onShowPress(mCurrentDownEvent);
break;
case LONG_PRESS:
dispatchLongPress();
break;
case TAP:
// If the user's finger is still down, do not count it as a tap
if (mDoubleTapListener != null) {
if (!mStillDown) {
mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
} else {
mDeferConfirmSingleTap = true;
}
}
break;
default:
throw new RuntimeException("Unknown message " + msg); //never
}
}
}
Recall the line mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
from the GestureDetector
. It delays notification of the tap by the timeout period for a valid double tap gesture. And the line if (hadTapMessage) mHandler.removeMessages(TAP);
from the GestureDetector
removes that notification upon a valid double tap. The GestureHandler
receives the tap notification after the delay and uses the callback to notify the GestureListener
with mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
. GestureHandler
will delay that callback to the MotionEvent.ACTION_UP
(handled by the GestureDetector
) in the event that the user's finger is down when the tap notification is received.
Besides what britzl suggests you may want to check your logic there for a second.
I don't think a double tap results in multiple, it simply results in 4 events, like you somewhat mention. While the Gesture libraries are (i think) the best choice, you should consider that:
- Store the Motionevent's timestamp (it's one of its methods) on ACTION_UP , then compare it with the next action up. Provided with a timeout, you will know when it is a tap or a double tap.
That is what the gesture listeners do