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