FileDocCategorySizeDatePackage
GestureDetector.javaAPI DocAndroid 5.1 API26868Thu Mar 12 22:22:10 GMT 2015android.view

GestureDetector

public class GestureDetector extends Object
Detects various gestures and events using the supplied {@link MotionEvent}s. The {@link OnGestureListener} callback will notify users when a particular motion event has occurred. This class should only be used with {@link MotionEvent}s reported via touch (don't use for trackball events). To use this class:
  • Create an instance of the {@code GestureDetector} for your {@link View}
  • In the {@link View#onTouchEvent(MotionEvent)} method ensure you call {@link #onTouchEvent(MotionEvent)}. The methods defined in your callback will be executed when the events occur.

Fields Summary
private int
mTouchSlopSquare
private int
mDoubleTapTouchSlopSquare
private int
mDoubleTapSlopSquare
private int
mMinimumFlingVelocity
private int
mMaximumFlingVelocity
private static final int
LONGPRESS_TIMEOUT
private static final int
TAP_TIMEOUT
private static final int
DOUBLE_TAP_TIMEOUT
private static final int
DOUBLE_TAP_MIN_TIME
private static final int
SHOW_PRESS
private static final int
LONG_PRESS
private static final int
TAP
private final android.os.Handler
mHandler
private final OnGestureListener
mListener
private OnDoubleTapListener
mDoubleTapListener
private boolean
mStillDown
private boolean
mDeferConfirmSingleTap
private boolean
mInLongPress
private boolean
mAlwaysInTapRegion
private boolean
mAlwaysInBiggerTapRegion
private MotionEvent
mCurrentDownEvent
private MotionEvent
mPreviousUpEvent
private boolean
mIsDoubleTapping
True when the user is still touching for the second tap (down, move, and up events). Can only be true if there is a double tap listener attached.
private float
mLastFocusX
private float
mLastFocusY
private float
mDownFocusX
private float
mDownFocusY
private boolean
mIsLongpressEnabled
private VelocityTracker
mVelocityTracker
Determines speed during touch scrolling
private final InputEventConsistencyVerifier
mInputEventConsistencyVerifier
Consistency verifier for debugging purposes.
Constructors Summary
public GestureDetector(OnGestureListener listener, android.os.Handler handler)
Creates a GestureDetector with the supplied listener. This variant of the constructor should be used from a non-UI thread (as it allows specifying the Handler).

param
listener the listener invoked for all the callbacks, this must not be null.
param
handler the handler to use
throws
NullPointerException if either {@code listener} or {@code handler} is null.
deprecated
Use {@link #GestureDetector(android.content.Context, android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead.

        this(null, listener, handler);
    
public GestureDetector(OnGestureListener listener)
Creates a GestureDetector with the supplied listener. You may only use this constructor from a UI thread (this is the usual situation).

see
android.os.Handler#Handler()
param
listener the listener invoked for all the callbacks, this must not be null.
throws
NullPointerException if {@code listener} is null.
deprecated
Use {@link #GestureDetector(android.content.Context, android.view.GestureDetector.OnGestureListener)} instead.

        this(null, listener, null);
    
public GestureDetector(android.content.Context context, OnGestureListener listener)
Creates a GestureDetector with the supplied listener. You may only use this constructor from a {@link android.os.Looper} thread.

see
android.os.Handler#Handler()
param
context the application's context
param
listener the listener invoked for all the callbacks, this must not be null.
throws
NullPointerException if {@code listener} is null.

        this(context, listener, null);
    
public GestureDetector(android.content.Context context, OnGestureListener listener, android.os.Handler handler)
Creates a GestureDetector with the supplied listener that runs deferred events on the thread associated with the supplied {@link android.os.Handler}.

see
android.os.Handler#Handler()
param
context the application's context
param
listener the listener invoked for all the callbacks, this must not be null.
param
handler the handler to use for running deferred listener events.
throws
NullPointerException if {@code listener} is null.

        if (handler != null) {
            mHandler = new GestureHandler(handler);
        } else {
            mHandler = new GestureHandler();
        }
        mListener = listener;
        if (listener instanceof OnDoubleTapListener) {
            setOnDoubleTapListener((OnDoubleTapListener) listener);
        }
        init(context);
    
public GestureDetector(android.content.Context context, OnGestureListener listener, android.os.Handler handler, boolean unused)
Creates a GestureDetector with the supplied listener that runs deferred events on the thread associated with the supplied {@link android.os.Handler}.

see
android.os.Handler#Handler()
param
context the application's context
param
listener the listener invoked for all the callbacks, this must not be null.
param
handler the handler to use for running deferred listener events.
param
unused currently not used.
throws
NullPointerException if {@code listener} is null.

        this(context, listener, handler);
    
Methods Summary
private voidcancel()

        mHandler.removeMessages(SHOW_PRESS);
        mHandler.removeMessages(LONG_PRESS);
        mHandler.removeMessages(TAP);
        mVelocityTracker.recycle();
        mVelocityTracker = null;
        mIsDoubleTapping = false;
        mStillDown = false;
        mAlwaysInTapRegion = false;
        mAlwaysInBiggerTapRegion = false;
        mDeferConfirmSingleTap = false;
        if (mInLongPress) {
            mInLongPress = false;
        }
    
private voidcancelTaps()

        mHandler.removeMessages(SHOW_PRESS);
        mHandler.removeMessages(LONG_PRESS);
        mHandler.removeMessages(TAP);
        mIsDoubleTapping = false;
        mAlwaysInTapRegion = false;
        mAlwaysInBiggerTapRegion = false;
        mDeferConfirmSingleTap = false;
        if (mInLongPress) {
            mInLongPress = false;
        }
    
private voiddispatchLongPress()

        mHandler.removeMessages(TAP);
        mDeferConfirmSingleTap = false;
        mInLongPress = true;
        mListener.onLongPress(mCurrentDownEvent);
    
private voidinit(android.content.Context context)

        if (mListener == null) {
            throw new NullPointerException("OnGestureListener must not be null");
        }
        mIsLongpressEnabled = true;

        // Fallback to support pre-donuts releases
        int touchSlop, doubleTapSlop, doubleTapTouchSlop;
        if (context == null) {
            //noinspection deprecation
            touchSlop = ViewConfiguration.getTouchSlop();
            doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this
            doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
            //noinspection deprecation
            mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
            mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
        } else {
            final ViewConfiguration configuration = ViewConfiguration.get(context);
            touchSlop = configuration.getScaledTouchSlop();
            doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop();
            doubleTapSlop = configuration.getScaledDoubleTapSlop();
            mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
            mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
        }
        mTouchSlopSquare = touchSlop * touchSlop;
        mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
        mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
    
private booleanisConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp, MotionEvent secondDown)

        if (!mAlwaysInBiggerTapRegion) {
            return false;
        }

        final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime();
        if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) {
            return false;
        }

        int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
        int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
        return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
    
public booleanisLongpressEnabled()

return
true if longpress is enabled, else false.

        return mIsLongpressEnabled;
    
public booleanonTouchEvent(MotionEvent ev)
Analyzes the given motion event and if applicable triggers the appropriate callbacks on the {@link OnGestureListener} supplied.

param
ev The current motion event.
return
true if the {@link OnGestureListener} consumed the event, else false.

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
        }

        final int action = ev.getAction();

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);

        final boolean pointerUp =
                (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
        final int skipIndex = pointerUp ? ev.getActionIndex() : -1;

        // Determine focal point
        float sumX = 0, sumY = 0;
        final int count = ev.getPointerCount();
        for (int i = 0; i < count; i++) {
            if (skipIndex == i) continue;
            sumX += ev.getX(i);
            sumY += ev.getY(i);
        }
        final int div = pointerUp ? count - 1 : count;
        final float focusX = sumX / div;
        final float focusY = sumY / div;

        boolean handled = false;

        switch (action & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_POINTER_DOWN:
            mDownFocusX = mLastFocusX = focusX;
            mDownFocusY = mLastFocusY = focusY;
            // Cancel long press and taps
            cancelTaps();
            break;

        case MotionEvent.ACTION_POINTER_UP:
            mDownFocusX = mLastFocusX = focusX;
            mDownFocusY = mLastFocusY = focusY;

            // Check the dot product of current velocities.
            // If the pointer that left was opposing another velocity vector, clear.
            mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
            final int upIndex = ev.getActionIndex();
            final int id1 = ev.getPointerId(upIndex);
            final float x1 = mVelocityTracker.getXVelocity(id1);
            final float y1 = mVelocityTracker.getYVelocity(id1);
            for (int i = 0; i < count; i++) {
                if (i == upIndex) continue;

                final int id2 = ev.getPointerId(i);
                final float x = x1 * mVelocityTracker.getXVelocity(id2);
                final float y = y1 * mVelocityTracker.getYVelocity(id2);

                final float dot = x + y;
                if (dot < 0) {
                    mVelocityTracker.clear();
                    break;
                }
            }
            break;

        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;

        case MotionEvent.ACTION_MOVE:
            if (mInLongPress) {
                break;
            }
            final float scrollX = mLastFocusX - focusX;
            final float scrollY = mLastFocusY - focusY;
            if (mIsDoubleTapping) {
                // Give the move events of the double-tap
                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
            } else if (mAlwaysInTapRegion) {
                final int deltaX = (int) (focusX - mDownFocusX);
                final int deltaY = (int) (focusY - mDownFocusY);
                int distance = (deltaX * deltaX) + (deltaY * deltaY);
                if (distance > mTouchSlopSquare) {
                    handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                    mLastFocusX = focusX;
                    mLastFocusY = focusY;
                    mAlwaysInTapRegion = false;
                    mHandler.removeMessages(TAP);
                    mHandler.removeMessages(SHOW_PRESS);
                    mHandler.removeMessages(LONG_PRESS);
                }
                if (distance > mDoubleTapTouchSlopSquare) {
                    mAlwaysInBiggerTapRegion = false;
                }
            } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
                handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                mLastFocusX = focusX;
                mLastFocusY = focusY;
            }
            break;

        case MotionEvent.ACTION_UP:
            mStillDown = false;
            MotionEvent currentUpEvent = MotionEvent.obtain(ev);
            if (mIsDoubleTapping) {
                // Finally, give the up event of the double-tap
                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
            } else if (mInLongPress) {
                mHandler.removeMessages(TAP);
                mInLongPress = false;
            } else if (mAlwaysInTapRegion) {
                handled = mListener.onSingleTapUp(ev);
                if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
                    mDoubleTapListener.onSingleTapConfirmed(ev);
                }
            } else {

                // A fling must travel the minimum tap distance
                final VelocityTracker velocityTracker = mVelocityTracker;
                final int pointerId = ev.getPointerId(0);
                velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
                final float velocityY = velocityTracker.getYVelocity(pointerId);
                final float velocityX = velocityTracker.getXVelocity(pointerId);

                if ((Math.abs(velocityY) > mMinimumFlingVelocity)
                        || (Math.abs(velocityX) > mMinimumFlingVelocity)){
                    handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
                }
            }
            if (mPreviousUpEvent != null) {
                mPreviousUpEvent.recycle();
            }
            // Hold the event we obtained above - listeners may have changed the original.
            mPreviousUpEvent = currentUpEvent;
            if (mVelocityTracker != null) {
                // This may have been cleared when we called out to the
                // application above.
                mVelocityTracker.recycle();
                mVelocityTracker = null;
            }
            mIsDoubleTapping = false;
            mDeferConfirmSingleTap = false;
            mHandler.removeMessages(SHOW_PRESS);
            mHandler.removeMessages(LONG_PRESS);
            break;

        case MotionEvent.ACTION_CANCEL:
            cancel();
            break;
        }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
        }
        return handled;
    
public voidsetIsLongpressEnabled(boolean isLongpressEnabled)
Set whether longpress is enabled, if this is enabled when a user presses and holds down you get a longpress event and nothing further. If it's disabled the user can press and hold down and then later moved their finger and you will get scroll events. By default longpress is enabled.

param
isLongpressEnabled whether longpress should be enabled.

        mIsLongpressEnabled = isLongpressEnabled;
    
public voidsetOnDoubleTapListener(android.view.GestureDetector$OnDoubleTapListener onDoubleTapListener)
Sets the listener which will be called for double-tap and related gestures.

param
onDoubleTapListener the listener invoked for all the callbacks, or null to stop listening for double-tap gestures.

        mDoubleTapListener = onDoubleTapListener;