19ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch/* 29ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch * Copyright (C) 2012 The Android Open Source Project 39ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch * 49ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch * Licensed under the Apache License, Version 2.0 (the "License"); 59ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch * you may not use this file except in compliance with the License. 69ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch * You may obtain a copy of the License at 79ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch * 89ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch * http://www.apache.org/licenses/LICENSE-2.0 9ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch * 109ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch * Unless required by applicable law or agreed to in writing, software 11ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch * distributed under the License is distributed on an "AS IS" BASIS, 129ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) * See the License for the specific language governing permissions and 14ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch * limitations under the License. 159ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch */ 169ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch 179ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdochpackage android.support.v4.view; 189ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch 19d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)import android.content.Context; 200f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)import android.os.Build; 219ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdochimport android.os.Handler; 229ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdochimport android.os.Message; 239ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdochimport android.view.GestureDetector; 249ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdochimport android.view.GestureDetector.OnDoubleTapListener; 25ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochimport android.view.GestureDetector.OnGestureListener; 269ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdochimport android.view.MotionEvent; 279ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdochimport android.view.VelocityTracker; 28ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochimport android.view.View; 29ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochimport android.view.ViewConfiguration; 300f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) 310f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)/** 320f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * Detects various gestures and events using the supplied {@link MotionEvent}s. 330f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * The {@link OnGestureListener} callback will notify users when a particular 340f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * motion event has occurred. This class should only be used with {@link MotionEvent}s 350f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * reported via touch (don't use for trackball events). 360f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * 370f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * <p>This compatibility implementation of the framework's GestureDetector guarantees 380f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * the newer focal point scrolling behavior from Jellybean MR1 on all platform versions.</p> 390f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * 400f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * To use this class: 410f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * <ul> 420f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * <li>Create an instance of the {@code GestureDetectorCompat} for your {@link View} 430f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call 440f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * {@link #onTouchEvent(MotionEvent)}. The methods defined in your callback 450f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * will be executed when the events occur. 460f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * </ul> 470f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) */ 480f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)public class GestureDetectorCompat { 490f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) interface GestureDetectorCompatImpl { 500f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) boolean isLongpressEnabled(); 510f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) boolean onTouchEvent(MotionEvent ev); 520f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) void setIsLongpressEnabled(boolean enabled); 530f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) void setOnDoubleTapListener(OnDoubleTapListener listener); 540f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) } 550f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) 560f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) static class GestureDetectorCompatImplBase implements GestureDetectorCompatImpl { 570f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) private int mTouchSlopSquare; 580f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) private int mDoubleTapSlopSquare; 590f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) private int mMinimumFlingVelocity; 60ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch private int mMaximumFlingVelocity; 61ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch 624e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(); 634e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout(); 644e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout(); 650f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) 660f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) // constants for Message.what used by GestureHandler below 670f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) private static final int SHOW_PRESS = 1; 680f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) private static final int LONG_PRESS = 2; 694e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) private static final int TAP = 3; 700f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) 710f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) private final Handler mHandler; 720f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) private final OnGestureListener mListener; 730f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) private OnDoubleTapListener mDoubleTapListener; 740f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) 750f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) private boolean mStillDown; 760f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) private boolean mDeferConfirmSingleTap; 770f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) private boolean mInLongPress; 780f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) private boolean mAlwaysInTapRegion; 790f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) private boolean mAlwaysInBiggerTapRegion; 800f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) 810f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) private MotionEvent mCurrentDownEvent; 820f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) private MotionEvent mPreviousUpEvent; 830f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) 840f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) /** 850f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * True when the user is still touching for the second tap (down, move, and 864e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) * up events). Can only be true if there is a double tap listener attached. 874e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) */ 880f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) private boolean mIsDoubleTapping; 890f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) 904e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) private float mLastFocusX; 914e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) private float mLastFocusY; 92ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch private float mDownFocusX; 93ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch private float mDownFocusY; 94ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch 95ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch private boolean mIsLongpressEnabled; 969ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch 979ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch /** 989ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch * Determines speed during touch scrolling 999ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch */ 1009ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch private VelocityTracker mVelocityTracker; 1019ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch 1029ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch private class GestureHandler extends Handler { 1039ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch GestureHandler() { 1049ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch super(); 1059ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch } 1069ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch 1079ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch GestureHandler(Handler handler) { 1089ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch super(handler.getLooper()); 1099ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch } 1109ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch 1119ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch @Override 1129ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch public void handleMessage(Message msg) { 1139ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch switch (msg.what) { 1149ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch case SHOW_PRESS: 1159ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch mListener.onShowPress(mCurrentDownEvent); 1169ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch break; 1179ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch 1189ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch case LONG_PRESS: 1199ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch dispatchLongPress(); 1209ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch break; 1219ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch 1229ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch case TAP: 1239ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch // If the user's finger is still down, do not count it as a tap 1249ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch if (mDoubleTapListener != null) { 1259ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch if (!mStillDown) { 126ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); 127ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch } else { 128ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch mDeferConfirmSingleTap = true; 1290f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) } 1300f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) } 131ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch break; 132ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch 133ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch default: 1340f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) throw new RuntimeException("Unknown message " + msg); //never 135ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch } 1364e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) } 137ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch } 138ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch 139ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch /** 1402385ea399aae016c0806a4f9ef3c9cfe3d2a39dfBen Murdoch * Creates a GestureDetector with the supplied listener. 141ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch * You may only use this constructor from a UI thread (this is the usual situation). 142ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch * @see android.os.Handler#Handler() 1430f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * 1440f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * @param context the application's context 1450f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * @param listener the listener invoked for all the callbacks, this must 1460f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * not be null. 1470f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * @param handler the handler to use 1480f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * 1490f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * @throws NullPointerException if {@code listener} is null. 1500f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) */ 1510f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) public GestureDetectorCompatImplBase(Context context, OnGestureListener listener, 1520f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) Handler handler) { 1530f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) if (handler != null) { 1540f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) mHandler = new GestureHandler(handler); 1550f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) } else { 1560f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) mHandler = new GestureHandler(); 1570f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) } 1580f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) mListener = listener; 1594e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) if (listener instanceof OnDoubleTapListener) { 1604e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) setOnDoubleTapListener((OnDoubleTapListener) listener); 1614e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) } 1624e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) init(context); 1630f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) } 1644e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) 1654e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) private void init(Context context) { 1664e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) if (context == null) { 1674e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) throw new IllegalArgumentException("Context must not be null"); 16868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) } 16968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) if (mListener == null) { 170d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) throw new IllegalArgumentException("OnGestureListener must not be null"); 171d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) } 172d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) mIsLongpressEnabled = true; 173d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 174d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) final ViewConfiguration configuration = ViewConfiguration.get(context); 175d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) final int touchSlop = configuration.getScaledTouchSlop(); 176d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) final int doubleTapSlop = configuration.getScaledDoubleTapSlop(); 177d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity(); 178d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity(); 179d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 180f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) mTouchSlopSquare = touchSlop * touchSlop; 181f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop; 182f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) } 183f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 184f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) /** 185f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) * Sets the listener which will be called for double-tap and related 186f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) * gestures. 187f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) * 188f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) * @param onDoubleTapListener the listener invoked for all the callbacks, or 189f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) * null to stop listening for double-tap gestures. 190f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) */ 1919ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) { 192 mDoubleTapListener = onDoubleTapListener; 193 } 194 195 /** 196 * Set whether longpress is enabled, if this is enabled when a user 197 * presses and holds down you get a longpress event and nothing further. 198 * If it's disabled the user can press and hold down and then later 199 * moved their finger and you will get scroll events. By default 200 * longpress is enabled. 201 * 202 * @param isLongpressEnabled whether longpress should be enabled. 203 */ 204 public void setIsLongpressEnabled(boolean isLongpressEnabled) { 205 mIsLongpressEnabled = isLongpressEnabled; 206 } 207 208 /** 209 * @return true if longpress is enabled, else false. 210 */ 211 public boolean isLongpressEnabled() { 212 return mIsLongpressEnabled; 213 } 214 215 /** 216 * Analyzes the given motion event and if applicable triggers the 217 * appropriate callbacks on the {@link OnGestureListener} supplied. 218 * 219 * @param ev The current motion event. 220 * @return true if the {@link OnGestureListener} consumed the event, 221 * else false. 222 */ 223 public boolean onTouchEvent(MotionEvent ev) { 224 final int action = ev.getAction(); 225 226 if (mVelocityTracker == null) { 227 mVelocityTracker = VelocityTracker.obtain(); 228 } 229 mVelocityTracker.addMovement(ev); 230 231 final boolean pointerUp = 232 (action & MotionEventCompat.ACTION_MASK) == MotionEventCompat.ACTION_POINTER_UP; 233 final int skipIndex = pointerUp ? MotionEventCompat.getActionIndex(ev) : -1; 234 235 // Determine focal point 236 float sumX = 0, sumY = 0; 237 final int count = MotionEventCompat.getPointerCount(ev); 238 for (int i = 0; i < count; i++) { 239 if (skipIndex == i) continue; 240 sumX += MotionEventCompat.getX(ev, i); 241 sumY += MotionEventCompat.getY(ev, i); 242 } 243 final int div = pointerUp ? count - 1 : count; 244 final float focusX = sumX / div; 245 final float focusY = sumY / div; 246 247 boolean handled = false; 248 249 switch (action & MotionEventCompat.ACTION_MASK) { 250 case MotionEventCompat.ACTION_POINTER_DOWN: 251 mDownFocusX = mLastFocusX = focusX; 252 mDownFocusY = mLastFocusY = focusY; 253 // Cancel long press and taps 254 cancelTaps(); 255 break; 256 257 case MotionEventCompat.ACTION_POINTER_UP: 258 mDownFocusX = mLastFocusX = focusX; 259 mDownFocusY = mLastFocusY = focusY; 260 261 // Check the dot product of current velocities. 262 // If the pointer that left was opposing another velocity vector, clear. 263 mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); 264 final int upIndex = MotionEventCompat.getActionIndex(ev); 265 final int id1 = MotionEventCompat.getPointerId(ev, upIndex); 266 final float x1 = VelocityTrackerCompat.getXVelocity(mVelocityTracker, id1); 267 final float y1 = VelocityTrackerCompat.getYVelocity(mVelocityTracker, id1); 268 for (int i = 0; i < count; i++) { 269 if (i == upIndex) continue; 270 271 final int id2 = MotionEventCompat.getPointerId(ev, i); 272 final float x = x1 * VelocityTrackerCompat.getXVelocity(mVelocityTracker, id2); 273 final float y = y1 * VelocityTrackerCompat.getYVelocity(mVelocityTracker, id2); 274 275 final float dot = x + y; 276 if (dot < 0) { 277 mVelocityTracker.clear(); 278 break; 279 } 280 } 281 break; 282 283 case MotionEvent.ACTION_DOWN: 284 if (mDoubleTapListener != null) { 285 boolean hadTapMessage = mHandler.hasMessages(TAP); 286 if (hadTapMessage) mHandler.removeMessages(TAP); 287 if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage && 288 isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) { 289 // This is a second tap 290 mIsDoubleTapping = true; 291 // Give a callback with the first tap of the double-tap 292 handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent); 293 // Give a callback with down event of the double-tap 294 handled |= mDoubleTapListener.onDoubleTapEvent(ev); 295 } else { 296 // This is a first tap 297 mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT); 298 } 299 } 300 301 mDownFocusX = mLastFocusX = focusX; 302 mDownFocusY = mLastFocusY = focusY; 303 if (mCurrentDownEvent != null) { 304 mCurrentDownEvent.recycle(); 305 } 306 mCurrentDownEvent = MotionEvent.obtain(ev); 307 mAlwaysInTapRegion = true; 308 mAlwaysInBiggerTapRegion = true; 309 mStillDown = true; 310 mInLongPress = false; 311 mDeferConfirmSingleTap = false; 312 313 if (mIsLongpressEnabled) { 314 mHandler.removeMessages(LONG_PRESS); 315 mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() 316 + TAP_TIMEOUT + LONGPRESS_TIMEOUT); 317 } 318 mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT); 319 handled |= mListener.onDown(ev); 320 break; 321 322 case MotionEvent.ACTION_MOVE: 323 if (mInLongPress) { 324 break; 325 } 326 final float scrollX = mLastFocusX - focusX; 327 final float scrollY = mLastFocusY - focusY; 328 if (mIsDoubleTapping) { 329 // Give the move events of the double-tap 330 handled |= mDoubleTapListener.onDoubleTapEvent(ev); 331 } else if (mAlwaysInTapRegion) { 332 final int deltaX = (int) (focusX - mDownFocusX); 333 final int deltaY = (int) (focusY - mDownFocusY); 334 int distance = (deltaX * deltaX) + (deltaY * deltaY); 335 if (distance > mTouchSlopSquare) { 336 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); 337 mLastFocusX = focusX; 338 mLastFocusY = focusY; 339 mAlwaysInTapRegion = false; 340 mHandler.removeMessages(TAP); 341 mHandler.removeMessages(SHOW_PRESS); 342 mHandler.removeMessages(LONG_PRESS); 343 } 344 if (distance > mTouchSlopSquare) { 345 mAlwaysInBiggerTapRegion = false; 346 } 347 } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) { 348 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); 349 mLastFocusX = focusX; 350 mLastFocusY = focusY; 351 } 352 break; 353 354 case MotionEvent.ACTION_UP: 355 mStillDown = false; 356 MotionEvent currentUpEvent = MotionEvent.obtain(ev); 357 if (mIsDoubleTapping) { 358 // Finally, give the up event of the double-tap 359 handled |= mDoubleTapListener.onDoubleTapEvent(ev); 360 } else if (mInLongPress) { 361 mHandler.removeMessages(TAP); 362 mInLongPress = false; 363 } else if (mAlwaysInTapRegion) { 364 handled = mListener.onSingleTapUp(ev); 365 if (mDeferConfirmSingleTap && mDoubleTapListener != null) { 366 mDoubleTapListener.onSingleTapConfirmed(ev); 367 } 368 } else { 369 // A fling must travel the minimum tap distance 370 final VelocityTracker velocityTracker = mVelocityTracker; 371 final int pointerId = MotionEventCompat.getPointerId(ev, 0); 372 velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); 373 final float velocityY = VelocityTrackerCompat.getYVelocity( 374 velocityTracker, pointerId); 375 final float velocityX = VelocityTrackerCompat.getXVelocity( 376 velocityTracker, pointerId); 377 378 if ((Math.abs(velocityY) > mMinimumFlingVelocity) 379 || (Math.abs(velocityX) > mMinimumFlingVelocity)){ 380 handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY); 381 } 382 } 383 if (mPreviousUpEvent != null) { 384 mPreviousUpEvent.recycle(); 385 } 386 // Hold the event we obtained above - listeners may have changed the original. 387 mPreviousUpEvent = currentUpEvent; 388 if (mVelocityTracker != null) { 389 // This may have been cleared when we called out to the 390 // application above. 391 mVelocityTracker.recycle(); 392 mVelocityTracker = null; 393 } 394 mIsDoubleTapping = false; 395 mDeferConfirmSingleTap = false; 396 mHandler.removeMessages(SHOW_PRESS); 397 mHandler.removeMessages(LONG_PRESS); 398 break; 399 400 case MotionEvent.ACTION_CANCEL: 401 cancel(); 402 break; 403 } 404 405 return handled; 406 } 407 408 private void cancel() { 409 mHandler.removeMessages(SHOW_PRESS); 410 mHandler.removeMessages(LONG_PRESS); 411 mHandler.removeMessages(TAP); 412 mVelocityTracker.recycle(); 413 mVelocityTracker = null; 414 mIsDoubleTapping = false; 415 mStillDown = false; 416 mAlwaysInTapRegion = false; 417 mAlwaysInBiggerTapRegion = false; 418 mDeferConfirmSingleTap = false; 419 if (mInLongPress) { 420 mInLongPress = false; 421 } 422 } 423 424 private void cancelTaps() { 425 mHandler.removeMessages(SHOW_PRESS); 426 mHandler.removeMessages(LONG_PRESS); 427 mHandler.removeMessages(TAP); 428 mIsDoubleTapping = false; 429 mAlwaysInTapRegion = false; 430 mAlwaysInBiggerTapRegion = false; 431 mDeferConfirmSingleTap = false; 432 if (mInLongPress) { 433 mInLongPress = false; 434 } 435 } 436 437 private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp, 438 MotionEvent secondDown) { 439 if (!mAlwaysInBiggerTapRegion) { 440 return false; 441 } 442 443 if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) { 444 return false; 445 } 446 447 int deltaX = (int) firstDown.getX() - (int) secondDown.getX(); 448 int deltaY = (int) firstDown.getY() - (int) secondDown.getY(); 449 return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare); 450 } 451 452 private void dispatchLongPress() { 453 mHandler.removeMessages(TAP); 454 mDeferConfirmSingleTap = false; 455 mInLongPress = true; 456 mListener.onLongPress(mCurrentDownEvent); 457 } 458 } 459 460 static class GestureDetectorCompatImplJellybeanMr2 implements GestureDetectorCompatImpl { 461 private final GestureDetector mDetector; 462 463 public GestureDetectorCompatImplJellybeanMr2(Context context, OnGestureListener listener, 464 Handler handler) { 465 mDetector = new GestureDetector(context, listener, handler); 466 } 467 468 @Override 469 public boolean isLongpressEnabled() { 470 return mDetector.isLongpressEnabled(); 471 } 472 473 @Override 474 public boolean onTouchEvent(MotionEvent ev) { 475 return mDetector.onTouchEvent(ev); 476 } 477 478 @Override 479 public void setIsLongpressEnabled(boolean enabled) { 480 mDetector.setIsLongpressEnabled(enabled); 481 } 482 483 @Override 484 public void setOnDoubleTapListener(OnDoubleTapListener listener) { 485 mDetector.setOnDoubleTapListener(listener); 486 } 487 } 488 489 private final GestureDetectorCompatImpl mImpl; 490 491 /** 492 * Creates a GestureDetectorCompat with the supplied listener. 493 * As usual, you may only use this constructor from a UI thread. 494 * @see android.os.Handler#Handler() 495 * 496 * @param context the application's context 497 * @param listener the listener invoked for all the callbacks, this must 498 * not be null. 499 */ 500 public GestureDetectorCompat(Context context, OnGestureListener listener) { 501 this(context, listener, null); 502 } 503 504 /** 505 * Creates a GestureDetectorCompat with the supplied listener. 506 * As usual, you may only use this constructor from a UI thread. 507 * @see android.os.Handler#Handler() 508 * 509 * @param context the application's context 510 * @param listener the listener invoked for all the callbacks, this must 511 * not be null. 512 * @param handler the handler that will be used for posting deferred messages 513 */ 514 public GestureDetectorCompat(Context context, OnGestureListener listener, Handler handler) { 515 if (Build.VERSION.SDK_INT > 17) { 516 mImpl = new GestureDetectorCompatImplJellybeanMr2(context, listener, handler); 517 } else { 518 mImpl = new GestureDetectorCompatImplBase(context, listener, handler); 519 } 520 } 521 522 /** 523 * @return true if longpress is enabled, else false. 524 */ 525 public boolean isLongpressEnabled() { 526 return mImpl.isLongpressEnabled(); 527 } 528 529 /** 530 * Analyzes the given motion event and if applicable triggers the 531 * appropriate callbacks on the {@link OnGestureListener} supplied. 532 * 533 * @param event The current motion event. 534 * @return true if the {@link OnGestureListener} consumed the event, 535 * else false. 536 */ 537 public boolean onTouchEvent(MotionEvent event) { 538 return mImpl.onTouchEvent(event); 539 } 540 541 /** 542 * Set whether longpress is enabled, if this is enabled when a user 543 * presses and holds down you get a longpress event and nothing further. 544 * If it's disabled the user can press and hold down and then later 545 * moved their finger and you will get scroll events. By default 546 * longpress is enabled. 547 * 548 * @param enabled whether longpress should be enabled. 549 */ 550 public void setIsLongpressEnabled(boolean enabled) { 551 mImpl.setIsLongpressEnabled(enabled); 552 } 553 554 /** 555 * Sets the listener which will be called for double-tap and related 556 * gestures. 557 * 558 * @param listener the listener invoked for all the callbacks, or 559 * null to stop listening for double-tap gestures. 560 */ 561 public void setOnDoubleTapListener(OnDoubleTapListener listener) { 562 mImpl.setOnDoubleTapListener(listener); 563 } 564} 565