1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.view; 18 19 import android.content.Context; 20 import android.os.Handler; 21 import android.os.Message; 22 23 /** 24 * Detects various gestures and events using the supplied {@link MotionEvent}s. 25 * The {@link OnGestureListener} callback will notify users when a particular 26 * motion event has occurred. This class should only be used with {@link MotionEvent}s 27 * reported via touch (don't use for trackball events). 28 * 29 * To use this class: 30 * <ul> 31 * <li>Create an instance of the {@code GestureDetector} for your {@link View} 32 * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call 33 * {@link #onTouchEvent(MotionEvent)}. The methods defined in your callback 34 * will be executed when the events occur. 35 * </ul> 36 */ 37 public class GestureDetector { 38 /** 39 * The listener that is used to notify when gestures occur. 40 * If you want to listen for all the different gestures then implement 41 * this interface. If you only want to listen for a subset it might 42 * be easier to extend {@link SimpleOnGestureListener}. 43 */ 44 public interface OnGestureListener { 45 46 /** 47 * Notified when a tap occurs with the down {@link MotionEvent} 48 * that triggered it. This will be triggered immediately for 49 * every down event. All other events should be preceded by this. 50 * 51 * @param e The down motion event. 52 */ onDown(MotionEvent e)53 boolean onDown(MotionEvent e); 54 55 /** 56 * The user has performed a down {@link MotionEvent} and not performed 57 * a move or up yet. This event is commonly used to provide visual 58 * feedback to the user to let them know that their action has been 59 * recognized i.e. highlight an element. 60 * 61 * @param e The down motion event 62 */ onShowPress(MotionEvent e)63 void onShowPress(MotionEvent e); 64 65 /** 66 * Notified when a tap occurs with the up {@link MotionEvent} 67 * that triggered it. 68 * 69 * @param e The up motion event that completed the first tap 70 * @return true if the event is consumed, else false 71 */ onSingleTapUp(MotionEvent e)72 boolean onSingleTapUp(MotionEvent e); 73 74 /** 75 * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the 76 * current move {@link MotionEvent}. The distance in x and y is also supplied for 77 * convenience. 78 * 79 * @param e1 The first down motion event that started the scrolling. 80 * @param e2 The move motion event that triggered the current onScroll. 81 * @param distanceX The distance along the X axis that has been scrolled since the last 82 * call to onScroll. This is NOT the distance between {@code e1} 83 * and {@code e2}. 84 * @param distanceY The distance along the Y axis that has been scrolled since the last 85 * call to onScroll. This is NOT the distance between {@code e1} 86 * and {@code e2}. 87 * @return true if the event is consumed, else false 88 */ onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)89 boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY); 90 91 /** 92 * Notified when a long press occurs with the initial on down {@link MotionEvent} 93 * that trigged it. 94 * 95 * @param e The initial on down motion event that started the longpress. 96 */ onLongPress(MotionEvent e)97 void onLongPress(MotionEvent e); 98 99 /** 100 * Notified of a fling event when it occurs with the initial on down {@link MotionEvent} 101 * and the matching up {@link MotionEvent}. The calculated velocity is supplied along 102 * the x and y axis in pixels per second. 103 * 104 * @param e1 The first down motion event that started the fling. 105 * @param e2 The move motion event that triggered the current onFling. 106 * @param velocityX The velocity of this fling measured in pixels per second 107 * along the x axis. 108 * @param velocityY The velocity of this fling measured in pixels per second 109 * along the y axis. 110 * @return true if the event is consumed, else false 111 */ onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)112 boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); 113 } 114 115 /** 116 * The listener that is used to notify when a double-tap or a confirmed 117 * single-tap occur. 118 */ 119 public interface OnDoubleTapListener { 120 /** 121 * Notified when a single-tap occurs. 122 * <p> 123 * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this 124 * will only be called after the detector is confident that the user's 125 * first tap is not followed by a second tap leading to a double-tap 126 * gesture. 127 * 128 * @param e The down motion event of the single-tap. 129 * @return true if the event is consumed, else false 130 */ onSingleTapConfirmed(MotionEvent e)131 boolean onSingleTapConfirmed(MotionEvent e); 132 133 /** 134 * Notified when a double-tap occurs. 135 * 136 * @param e The down motion event of the first tap of the double-tap. 137 * @return true if the event is consumed, else false 138 */ onDoubleTap(MotionEvent e)139 boolean onDoubleTap(MotionEvent e); 140 141 /** 142 * Notified when an event within a double-tap gesture occurs, including 143 * the down, move, and up events. 144 * 145 * @param e The motion event that occurred during the double-tap gesture. 146 * @return true if the event is consumed, else false 147 */ onDoubleTapEvent(MotionEvent e)148 boolean onDoubleTapEvent(MotionEvent e); 149 } 150 151 /** 152 * A convenience class to extend when you only want to listen for a subset 153 * of all the gestures. This implements all methods in the 154 * {@link OnGestureListener} and {@link OnDoubleTapListener} but does 155 * nothing and return {@code false} for all applicable methods. 156 */ 157 public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener { onSingleTapUp(MotionEvent e)158 public boolean onSingleTapUp(MotionEvent e) { 159 return false; 160 } 161 onLongPress(MotionEvent e)162 public void onLongPress(MotionEvent e) { 163 } 164 onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)165 public boolean onScroll(MotionEvent e1, MotionEvent e2, 166 float distanceX, float distanceY) { 167 return false; 168 } 169 onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)170 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, 171 float velocityY) { 172 return false; 173 } 174 onShowPress(MotionEvent e)175 public void onShowPress(MotionEvent e) { 176 } 177 onDown(MotionEvent e)178 public boolean onDown(MotionEvent e) { 179 return false; 180 } 181 onDoubleTap(MotionEvent e)182 public boolean onDoubleTap(MotionEvent e) { 183 return false; 184 } 185 onDoubleTapEvent(MotionEvent e)186 public boolean onDoubleTapEvent(MotionEvent e) { 187 return false; 188 } 189 onSingleTapConfirmed(MotionEvent e)190 public boolean onSingleTapConfirmed(MotionEvent e) { 191 return false; 192 } 193 } 194 195 private int mTouchSlopSquare; 196 private int mDoubleTapTouchSlopSquare; 197 private int mDoubleTapSlopSquare; 198 private int mMinimumFlingVelocity; 199 private int mMaximumFlingVelocity; 200 201 private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(); 202 private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout(); 203 private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout(); 204 private static final int DOUBLE_TAP_MIN_TIME = ViewConfiguration.getDoubleTapMinTime(); 205 206 // constants for Message.what used by GestureHandler below 207 private static final int SHOW_PRESS = 1; 208 private static final int LONG_PRESS = 2; 209 private static final int TAP = 3; 210 211 private final Handler mHandler; 212 private final OnGestureListener mListener; 213 private OnDoubleTapListener mDoubleTapListener; 214 215 private boolean mStillDown; 216 private boolean mDeferConfirmSingleTap; 217 private boolean mInLongPress; 218 private boolean mAlwaysInTapRegion; 219 private boolean mAlwaysInBiggerTapRegion; 220 221 private MotionEvent mCurrentDownEvent; 222 private MotionEvent mPreviousUpEvent; 223 224 /** 225 * True when the user is still touching for the second tap (down, move, and 226 * up events). Can only be true if there is a double tap listener attached. 227 */ 228 private boolean mIsDoubleTapping; 229 230 private float mLastFocusX; 231 private float mLastFocusY; 232 private float mDownFocusX; 233 private float mDownFocusY; 234 235 private boolean mIsLongpressEnabled; 236 237 /** 238 * Determines speed during touch scrolling 239 */ 240 private VelocityTracker mVelocityTracker; 241 242 /** 243 * Consistency verifier for debugging purposes. 244 */ 245 private final InputEventConsistencyVerifier mInputEventConsistencyVerifier = 246 InputEventConsistencyVerifier.isInstrumentationEnabled() ? 247 new InputEventConsistencyVerifier(this, 0) : null; 248 249 private class GestureHandler extends Handler { GestureHandler()250 GestureHandler() { 251 super(); 252 } 253 GestureHandler(Handler handler)254 GestureHandler(Handler handler) { 255 super(handler.getLooper()); 256 } 257 258 @Override handleMessage(Message msg)259 public void handleMessage(Message msg) { 260 switch (msg.what) { 261 case SHOW_PRESS: 262 mListener.onShowPress(mCurrentDownEvent); 263 break; 264 265 case LONG_PRESS: 266 dispatchLongPress(); 267 break; 268 269 case TAP: 270 // If the user's finger is still down, do not count it as a tap 271 if (mDoubleTapListener != null) { 272 if (!mStillDown) { 273 mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); 274 } else { 275 mDeferConfirmSingleTap = true; 276 } 277 } 278 break; 279 280 default: 281 throw new RuntimeException("Unknown message " + msg); //never 282 } 283 } 284 } 285 286 /** 287 * Creates a GestureDetector with the supplied listener. 288 * This variant of the constructor should be used from a non-UI thread 289 * (as it allows specifying the Handler). 290 * 291 * @param listener the listener invoked for all the callbacks, this must 292 * not be null. 293 * @param handler the handler to use 294 * 295 * @throws NullPointerException if either {@code listener} or 296 * {@code handler} is null. 297 * 298 * @deprecated Use {@link #GestureDetector(android.content.Context, 299 * android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead. 300 */ 301 @Deprecated GestureDetector(OnGestureListener listener, Handler handler)302 public GestureDetector(OnGestureListener listener, Handler handler) { 303 this(null, listener, handler); 304 } 305 306 /** 307 * Creates a GestureDetector with the supplied listener. 308 * You may only use this constructor from a UI thread (this is the usual situation). 309 * @see android.os.Handler#Handler() 310 * 311 * @param listener the listener invoked for all the callbacks, this must 312 * not be null. 313 * 314 * @throws NullPointerException if {@code listener} is null. 315 * 316 * @deprecated Use {@link #GestureDetector(android.content.Context, 317 * android.view.GestureDetector.OnGestureListener)} instead. 318 */ 319 @Deprecated GestureDetector(OnGestureListener listener)320 public GestureDetector(OnGestureListener listener) { 321 this(null, listener, null); 322 } 323 324 /** 325 * Creates a GestureDetector with the supplied listener. 326 * You may only use this constructor from a {@link android.os.Looper} thread. 327 * @see android.os.Handler#Handler() 328 * 329 * @param context the application's context 330 * @param listener the listener invoked for all the callbacks, this must 331 * not be null. 332 * 333 * @throws NullPointerException if {@code listener} is null. 334 */ GestureDetector(Context context, OnGestureListener listener)335 public GestureDetector(Context context, OnGestureListener listener) { 336 this(context, listener, null); 337 } 338 339 /** 340 * Creates a GestureDetector with the supplied listener that runs deferred events on the 341 * thread associated with the supplied {@link android.os.Handler}. 342 * @see android.os.Handler#Handler() 343 * 344 * @param context the application's context 345 * @param listener the listener invoked for all the callbacks, this must 346 * not be null. 347 * @param handler the handler to use for running deferred listener events. 348 * 349 * @throws NullPointerException if {@code listener} is null. 350 */ GestureDetector(Context context, OnGestureListener listener, Handler handler)351 public GestureDetector(Context context, OnGestureListener listener, Handler handler) { 352 if (handler != null) { 353 mHandler = new GestureHandler(handler); 354 } else { 355 mHandler = new GestureHandler(); 356 } 357 mListener = listener; 358 if (listener instanceof OnDoubleTapListener) { 359 setOnDoubleTapListener((OnDoubleTapListener) listener); 360 } 361 init(context); 362 } 363 364 /** 365 * Creates a GestureDetector with the supplied listener that runs deferred events on the 366 * thread associated with the supplied {@link android.os.Handler}. 367 * @see android.os.Handler#Handler() 368 * 369 * @param context the application's context 370 * @param listener the listener invoked for all the callbacks, this must 371 * not be null. 372 * @param handler the handler to use for running deferred listener events. 373 * @param unused currently not used. 374 * 375 * @throws NullPointerException if {@code listener} is null. 376 */ GestureDetector(Context context, OnGestureListener listener, Handler handler, boolean unused)377 public GestureDetector(Context context, OnGestureListener listener, Handler handler, 378 boolean unused) { 379 this(context, listener, handler); 380 } 381 init(Context context)382 private void init(Context context) { 383 if (mListener == null) { 384 throw new NullPointerException("OnGestureListener must not be null"); 385 } 386 mIsLongpressEnabled = true; 387 388 // Fallback to support pre-donuts releases 389 int touchSlop, doubleTapSlop, doubleTapTouchSlop; 390 if (context == null) { 391 //noinspection deprecation 392 touchSlop = ViewConfiguration.getTouchSlop(); 393 doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this 394 doubleTapSlop = ViewConfiguration.getDoubleTapSlop(); 395 //noinspection deprecation 396 mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity(); 397 mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity(); 398 } else { 399 final ViewConfiguration configuration = ViewConfiguration.get(context); 400 touchSlop = configuration.getScaledTouchSlop(); 401 doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop(); 402 doubleTapSlop = configuration.getScaledDoubleTapSlop(); 403 mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity(); 404 mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity(); 405 } 406 mTouchSlopSquare = touchSlop * touchSlop; 407 mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop; 408 mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop; 409 } 410 411 /** 412 * Sets the listener which will be called for double-tap and related 413 * gestures. 414 * 415 * @param onDoubleTapListener the listener invoked for all the callbacks, or 416 * null to stop listening for double-tap gestures. 417 */ setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener)418 public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) { 419 mDoubleTapListener = onDoubleTapListener; 420 } 421 422 /** 423 * Set whether longpress is enabled, if this is enabled when a user 424 * presses and holds down you get a longpress event and nothing further. 425 * If it's disabled the user can press and hold down and then later 426 * moved their finger and you will get scroll events. By default 427 * longpress is enabled. 428 * 429 * @param isLongpressEnabled whether longpress should be enabled. 430 */ setIsLongpressEnabled(boolean isLongpressEnabled)431 public void setIsLongpressEnabled(boolean isLongpressEnabled) { 432 mIsLongpressEnabled = isLongpressEnabled; 433 } 434 435 /** 436 * @return true if longpress is enabled, else false. 437 */ isLongpressEnabled()438 public boolean isLongpressEnabled() { 439 return mIsLongpressEnabled; 440 } 441 442 /** 443 * Analyzes the given motion event and if applicable triggers the 444 * appropriate callbacks on the {@link OnGestureListener} supplied. 445 * 446 * @param ev The current motion event. 447 * @return true if the {@link OnGestureListener} consumed the event, 448 * else false. 449 */ onTouchEvent(MotionEvent ev)450 public boolean onTouchEvent(MotionEvent ev) { 451 if (mInputEventConsistencyVerifier != null) { 452 mInputEventConsistencyVerifier.onTouchEvent(ev, 0); 453 } 454 455 final int action = ev.getAction(); 456 457 if (mVelocityTracker == null) { 458 mVelocityTracker = VelocityTracker.obtain(); 459 } 460 mVelocityTracker.addMovement(ev); 461 462 final boolean pointerUp = 463 (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP; 464 final int skipIndex = pointerUp ? ev.getActionIndex() : -1; 465 466 // Determine focal point 467 float sumX = 0, sumY = 0; 468 final int count = ev.getPointerCount(); 469 for (int i = 0; i < count; i++) { 470 if (skipIndex == i) continue; 471 sumX += ev.getX(i); 472 sumY += ev.getY(i); 473 } 474 final int div = pointerUp ? count - 1 : count; 475 final float focusX = sumX / div; 476 final float focusY = sumY / div; 477 478 boolean handled = false; 479 480 switch (action & MotionEvent.ACTION_MASK) { 481 case MotionEvent.ACTION_POINTER_DOWN: 482 mDownFocusX = mLastFocusX = focusX; 483 mDownFocusY = mLastFocusY = focusY; 484 // Cancel long press and taps 485 cancelTaps(); 486 break; 487 488 case MotionEvent.ACTION_POINTER_UP: 489 mDownFocusX = mLastFocusX = focusX; 490 mDownFocusY = mLastFocusY = focusY; 491 492 // Check the dot product of current velocities. 493 // If the pointer that left was opposing another velocity vector, clear. 494 mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); 495 final int upIndex = ev.getActionIndex(); 496 final int id1 = ev.getPointerId(upIndex); 497 final float x1 = mVelocityTracker.getXVelocity(id1); 498 final float y1 = mVelocityTracker.getYVelocity(id1); 499 for (int i = 0; i < count; i++) { 500 if (i == upIndex) continue; 501 502 final int id2 = ev.getPointerId(i); 503 final float x = x1 * mVelocityTracker.getXVelocity(id2); 504 final float y = y1 * mVelocityTracker.getYVelocity(id2); 505 506 final float dot = x + y; 507 if (dot < 0) { 508 mVelocityTracker.clear(); 509 break; 510 } 511 } 512 break; 513 514 case MotionEvent.ACTION_DOWN: 515 if (mDoubleTapListener != null) { 516 boolean hadTapMessage = mHandler.hasMessages(TAP); 517 if (hadTapMessage) mHandler.removeMessages(TAP); 518 if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage && 519 isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) { 520 // This is a second tap 521 mIsDoubleTapping = true; 522 // Give a callback with the first tap of the double-tap 523 handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent); 524 // Give a callback with down event of the double-tap 525 handled |= mDoubleTapListener.onDoubleTapEvent(ev); 526 } else { 527 // This is a first tap 528 mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT); 529 } 530 } 531 532 mDownFocusX = mLastFocusX = focusX; 533 mDownFocusY = mLastFocusY = focusY; 534 if (mCurrentDownEvent != null) { 535 mCurrentDownEvent.recycle(); 536 } 537 mCurrentDownEvent = MotionEvent.obtain(ev); 538 mAlwaysInTapRegion = true; 539 mAlwaysInBiggerTapRegion = true; 540 mStillDown = true; 541 mInLongPress = false; 542 mDeferConfirmSingleTap = false; 543 544 if (mIsLongpressEnabled) { 545 mHandler.removeMessages(LONG_PRESS); 546 mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() 547 + TAP_TIMEOUT + LONGPRESS_TIMEOUT); 548 } 549 mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT); 550 handled |= mListener.onDown(ev); 551 break; 552 553 case MotionEvent.ACTION_MOVE: 554 if (mInLongPress) { 555 break; 556 } 557 final float scrollX = mLastFocusX - focusX; 558 final float scrollY = mLastFocusY - focusY; 559 if (mIsDoubleTapping) { 560 // Give the move events of the double-tap 561 handled |= mDoubleTapListener.onDoubleTapEvent(ev); 562 } else if (mAlwaysInTapRegion) { 563 final int deltaX = (int) (focusX - mDownFocusX); 564 final int deltaY = (int) (focusY - mDownFocusY); 565 int distance = (deltaX * deltaX) + (deltaY * deltaY); 566 if (distance > mTouchSlopSquare) { 567 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); 568 mLastFocusX = focusX; 569 mLastFocusY = focusY; 570 mAlwaysInTapRegion = false; 571 mHandler.removeMessages(TAP); 572 mHandler.removeMessages(SHOW_PRESS); 573 mHandler.removeMessages(LONG_PRESS); 574 } 575 if (distance > mDoubleTapTouchSlopSquare) { 576 mAlwaysInBiggerTapRegion = false; 577 } 578 } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) { 579 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); 580 mLastFocusX = focusX; 581 mLastFocusY = focusY; 582 } 583 break; 584 585 case MotionEvent.ACTION_UP: 586 mStillDown = false; 587 MotionEvent currentUpEvent = MotionEvent.obtain(ev); 588 if (mIsDoubleTapping) { 589 // Finally, give the up event of the double-tap 590 handled |= mDoubleTapListener.onDoubleTapEvent(ev); 591 } else if (mInLongPress) { 592 mHandler.removeMessages(TAP); 593 mInLongPress = false; 594 } else if (mAlwaysInTapRegion) { 595 handled = mListener.onSingleTapUp(ev); 596 if (mDeferConfirmSingleTap && mDoubleTapListener != null) { 597 mDoubleTapListener.onSingleTapConfirmed(ev); 598 } 599 } else { 600 601 // A fling must travel the minimum tap distance 602 final VelocityTracker velocityTracker = mVelocityTracker; 603 final int pointerId = ev.getPointerId(0); 604 velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); 605 final float velocityY = velocityTracker.getYVelocity(pointerId); 606 final float velocityX = velocityTracker.getXVelocity(pointerId); 607 608 if ((Math.abs(velocityY) > mMinimumFlingVelocity) 609 || (Math.abs(velocityX) > mMinimumFlingVelocity)){ 610 handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY); 611 } 612 } 613 if (mPreviousUpEvent != null) { 614 mPreviousUpEvent.recycle(); 615 } 616 // Hold the event we obtained above - listeners may have changed the original. 617 mPreviousUpEvent = currentUpEvent; 618 if (mVelocityTracker != null) { 619 // This may have been cleared when we called out to the 620 // application above. 621 mVelocityTracker.recycle(); 622 mVelocityTracker = null; 623 } 624 mIsDoubleTapping = false; 625 mDeferConfirmSingleTap = false; 626 mHandler.removeMessages(SHOW_PRESS); 627 mHandler.removeMessages(LONG_PRESS); 628 break; 629 630 case MotionEvent.ACTION_CANCEL: 631 cancel(); 632 break; 633 } 634 635 if (!handled && mInputEventConsistencyVerifier != null) { 636 mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0); 637 } 638 return handled; 639 } 640 cancel()641 private void cancel() { 642 mHandler.removeMessages(SHOW_PRESS); 643 mHandler.removeMessages(LONG_PRESS); 644 mHandler.removeMessages(TAP); 645 mVelocityTracker.recycle(); 646 mVelocityTracker = null; 647 mIsDoubleTapping = false; 648 mStillDown = false; 649 mAlwaysInTapRegion = false; 650 mAlwaysInBiggerTapRegion = false; 651 mDeferConfirmSingleTap = false; 652 if (mInLongPress) { 653 mInLongPress = false; 654 } 655 } 656 cancelTaps()657 private void cancelTaps() { 658 mHandler.removeMessages(SHOW_PRESS); 659 mHandler.removeMessages(LONG_PRESS); 660 mHandler.removeMessages(TAP); 661 mIsDoubleTapping = false; 662 mAlwaysInTapRegion = false; 663 mAlwaysInBiggerTapRegion = false; 664 mDeferConfirmSingleTap = false; 665 if (mInLongPress) { 666 mInLongPress = false; 667 } 668 } 669 isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp, MotionEvent secondDown)670 private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp, 671 MotionEvent secondDown) { 672 if (!mAlwaysInBiggerTapRegion) { 673 return false; 674 } 675 676 final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime(); 677 if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) { 678 return false; 679 } 680 681 int deltaX = (int) firstDown.getX() - (int) secondDown.getX(); 682 int deltaY = (int) firstDown.getY() - (int) secondDown.getY(); 683 return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare); 684 } 685 dispatchLongPress()686 private void dispatchLongPress() { 687 mHandler.removeMessages(TAP); 688 mDeferConfirmSingleTap = false; 689 mInLongPress = true; 690 mListener.onLongPress(mCurrentDownEvent); 691 } 692 } 693