1 /* 2 * Copyright (C) 2016 Google Inc. 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 com.android.setupwizardlib.gesture; 18 19 import android.graphics.Rect; 20 import android.view.MotionEvent; 21 import android.view.View; 22 import android.view.ViewConfiguration; 23 24 /** 25 * Helper class to detect the consective-tap gestures on a view. 26 * 27 * <p>This class is instantiated and used similar to a GestureDetector, where onTouchEvent should be 28 * called when there are MotionEvents this detector should know about. 29 */ 30 public final class ConsecutiveTapsGestureDetector { 31 32 public interface OnConsecutiveTapsListener { 33 /** Callback method when the user tapped on the target view X number of times. */ onConsecutiveTaps(int numOfConsecutiveTaps)34 void onConsecutiveTaps(int numOfConsecutiveTaps); 35 } 36 37 private final View view; 38 private final OnConsecutiveTapsListener listener; 39 private final int consecutiveTapTouchSlopSquare; 40 private final int consecutiveTapTimeout; 41 42 private int consecutiveTapsCounter = 0; 43 private MotionEvent previousTapEvent; 44 45 /** 46 * @param listener The listener that responds to the gesture. 47 * @param view The target view that associated with consecutive-tap gesture. 48 */ ConsecutiveTapsGestureDetector(OnConsecutiveTapsListener listener, View view)49 public ConsecutiveTapsGestureDetector(OnConsecutiveTapsListener listener, View view) { 50 this.listener = listener; 51 this.view = view; 52 int doubleTapSlop = ViewConfiguration.get(this.view.getContext()).getScaledDoubleTapSlop(); 53 consecutiveTapTouchSlopSquare = doubleTapSlop * doubleTapSlop; 54 consecutiveTapTimeout = ViewConfiguration.getDoubleTapTimeout(); 55 } 56 57 /** 58 * This method should be called from the relevant activity or view, typically in onTouchEvent, 59 * onInterceptTouchEvent or dispatchTouchEvent. 60 * 61 * @param ev The motion event 62 */ onTouchEvent(MotionEvent ev)63 public void onTouchEvent(MotionEvent ev) { 64 if (ev.getAction() == MotionEvent.ACTION_UP) { 65 Rect viewRect = new Rect(); 66 int[] leftTop = new int[2]; 67 view.getLocationOnScreen(leftTop); 68 viewRect.set( 69 leftTop[0], leftTop[1], leftTop[0] + view.getWidth(), leftTop[1] + view.getHeight()); 70 if (viewRect.contains((int) ev.getX(), (int) ev.getY())) { 71 if (isConsecutiveTap(ev)) { 72 consecutiveTapsCounter++; 73 } else { 74 consecutiveTapsCounter = 1; 75 } 76 listener.onConsecutiveTaps(consecutiveTapsCounter); 77 } else { 78 // Touch outside the target view. Reset counter. 79 consecutiveTapsCounter = 0; 80 } 81 82 if (previousTapEvent != null) { 83 previousTapEvent.recycle(); 84 } 85 previousTapEvent = MotionEvent.obtain(ev); 86 } 87 } 88 89 /** Resets the consecutive-tap counter to zero. */ resetCounter()90 public void resetCounter() { 91 consecutiveTapsCounter = 0; 92 } 93 94 /** 95 * Returns true if the distance between consecutive tap is within {@link 96 * #consecutiveTapTouchSlopSquare}. False, otherwise. 97 */ isConsecutiveTap(MotionEvent currentTapEvent)98 private boolean isConsecutiveTap(MotionEvent currentTapEvent) { 99 if (previousTapEvent == null) { 100 return false; 101 } 102 103 double deltaX = previousTapEvent.getX() - currentTapEvent.getX(); 104 double deltaY = previousTapEvent.getY() - currentTapEvent.getY(); 105 long deltaTime = currentTapEvent.getEventTime() - previousTapEvent.getEventTime(); 106 return (deltaX * deltaX + deltaY * deltaY <= consecutiveTapTouchSlopSquare) 107 && deltaTime < consecutiveTapTimeout; 108 } 109 } 110