/* * This file provided by Facebook is for non-commercial testing and evaluation * purposes only. Facebook reserves all rights not expressly granted. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.facebook.samples.gestures; import android.annotation.TargetApi; import android.os.Build; import android.view.MotionEvent; /** * Component that detects and tracks multiple pointers based on touch events. * <p> * Each time a pointer gets pressed or released, the current gesture (if any) will end, and a new * one will be started (if there are still pressed pointers left). It is guaranteed that the number * of pointers within the single gesture will remain the same during the whole gesture. */ public class MultiPointerGestureDetector { /** The listener for receiving notifications when gestures occur. */ public interface Listener { /** Responds to the beginning of a gesture. */ public void onGestureBegin(MultiPointerGestureDetector detector); /** Responds to the update of a gesture in progress. */ public void onGestureUpdate(MultiPointerGestureDetector detector); /** Responds to the end of a gesture. */ public void onGestureEnd(MultiPointerGestureDetector detector); } private static final int MAX_POINTERS = 2; private boolean mGestureInProgress; private int mCount; private final int mId[] = new int[MAX_POINTERS]; private final float mStartX[] = new float[MAX_POINTERS]; private final float mStartY[] = new float[MAX_POINTERS]; private final float mCurrentX[] = new float[MAX_POINTERS]; private final float mCurrentY[] = new float[MAX_POINTERS]; private Listener mListener = null; public MultiPointerGestureDetector() { reset(); } /** Factory method that creates a new instance of MultiPointerGestureDetector */ public static MultiPointerGestureDetector newInstance() { return new MultiPointerGestureDetector(); } /** * Sets the listener. * @param listener listener to set */ public void setListener(Listener listener) { mListener = listener; } /** * Resets the component to the initial state. */ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public void reset() { mGestureInProgress = false; mCount = 0; for (int i = 0; i < MAX_POINTERS; i++) { mId[i] = Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH ? -1 : MotionEvent.INVALID_POINTER_ID; } } /** * This method can be overridden in order to perform threshold check or something similar. * @return whether or not to start a new gesture */ protected boolean shouldStartGesture() { return true; } private void startGesture() { if (!mGestureInProgress) { mGestureInProgress = true; if (mListener != null) { mListener.onGestureBegin(this); } } } private void stopGesture() { if (mGestureInProgress) { mGestureInProgress = false; if (mListener != null) { mListener.onGestureEnd(this); } } } /** * Gets the index of the i-th pressed pointer. * Normally, the index will be equal to i, except in the case when the pointer is released. * @return index of the specified pointer or -1 if not found (i.e. not enough pointers are down) */ private int getPressedPointerIndex(MotionEvent event, int i) { final int count = event.getPointerCount(); final int action = event.getActionMasked(); final int index = event.getActionIndex(); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) { if (i >= index) { i++; } } return (i < count) ? i : -1; } /** * Handles the given motion event. * @param event event to handle * @return whether or not the event was handled */ public boolean onTouchEvent(final MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_MOVE: { // update pointers for (int i = 0; i < MAX_POINTERS; i++) { int index = event.findPointerIndex(mId[i]); if (index != -1) { mCurrentX[i] = event.getX(index); mCurrentY[i] = event.getY(index); } } // start a new gesture if not already started if (!mGestureInProgress && shouldStartGesture()) { startGesture(); } // notify listener if (mGestureInProgress && mListener != null) { mListener.onGestureUpdate(this); } break; } case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_UP: { // we'll restart the current gesture (if any) whenever the number of pointers changes // NOTE: we only restart existing gestures here, new gestures are started in ACTION_MOVE boolean wasGestureInProgress = mGestureInProgress; stopGesture(); reset(); // update pointers for (int i = 0; i < MAX_POINTERS; i++) { int index = getPressedPointerIndex(event, i); if (index == -1) { break; } mId[i] = event.getPointerId(index); mCurrentX[i] = mStartX[i] = event.getX(index); mCurrentY[i] = mStartY[i] = event.getY(index); mCount++; } // restart the gesture (if any) if there are still pointers left if (wasGestureInProgress && mCount > 0) { startGesture(); } break; } case MotionEvent.ACTION_CANCEL: { stopGesture(); reset(); break; } } return true; } /** Restarts the current gesture */ public void restartGesture() { if (!mGestureInProgress) { return; } stopGesture(); for (int i = 0; i < MAX_POINTERS; i++) { mStartX[i] = mCurrentX[i]; mStartY[i] = mCurrentY[i]; } startGesture(); } /** Gets whether gesture is in progress or not */ public boolean isGestureInProgress() { return mGestureInProgress; } /** Gets the number of pointers in the current gesture */ public int getCount() { return mCount; } /** * Gets the start X coordinates for the all pointers * Mutable array is exposed for performance reasons and is not to be modified by the callers. */ public float[] getStartX() { return mStartX; } /** * Gets the start Y coordinates for the all pointers * Mutable array is exposed for performance reasons and is not to be modified by the callers. */ public float[] getStartY() { return mStartY; } /** * Gets the current X coordinates for the all pointers * Mutable array is exposed for performance reasons and is not to be modified by the callers. */ public float[] getCurrentX() { return mCurrentX; } /** * Gets the current Y coordinates for the all pointers * Mutable array is exposed for performance reasons and is not to be modified by the callers. */ public float[] getCurrentY() { return mCurrentY; } }