/*
* 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.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 {
/** A callback called right before the gesture is about to start. */
public void onGestureBegin(MultiPointerGestureDetector detector);
/** A callback called each time the gesture gets updated. */
public void onGestureUpdate(MultiPointerGestureDetector detector);
/** A callback called right after the gesture has finished. */
public void onGestureEnd(MultiPointerGestureDetector detector);
}
private static final int MAX_POINTERS = 2;
private boolean mGestureInProgress;
private int mPointerCount;
private int mNewPointerCount;
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.
*/
public void reset() {
mGestureInProgress = false;
mPointerCount = 0;
for (int i = 0; i < MAX_POINTERS; i++) {
mId[i] = 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;
}
/**
* Starts a new gesture and calls the listener just before starting it.
*/
private void startGesture() {
if (!mGestureInProgress) {
if (mListener != null) {
mListener.onGestureBegin(this);
}
mGestureInProgress = true;
}
}
/**
* Stops the current gesture and calls the listener right after stopping it.
*/
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;
}
/**
* Gets the number of pressed pointers (fingers down).
*/
private static int getPressedPointerCount(MotionEvent event) {
int count = event.getPointerCount();
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_POINTER_UP) {
count--;
}
return count;
}
private void updatePointersOnTap(MotionEvent event) {
mPointerCount = 0;
for (int i = 0; i < MAX_POINTERS; i++) {
int index = getPressedPointerIndex(event, i);
if (index == -1) {
mId[i] = MotionEvent.INVALID_POINTER_ID;
} else {
mId[i] = event.getPointerId(index);
mCurrentX[i] = mStartX[i] = event.getX(index);
mCurrentY[i] = mStartY[i] = event.getY(index);
mPointerCount++;
}
}
}
private void updatePointersOnMove(MotionEvent event) {
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);
}
}
}
/**
* 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
updatePointersOnMove(event);
// start a new gesture if not already started
if (!mGestureInProgress && mPointerCount > 0 && 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: {
// restart gesture whenever the number of pointers changes
mNewPointerCount = getPressedPointerCount(event);
stopGesture();
updatePointersOnTap(event);
if (mPointerCount > 0 && shouldStartGesture()) {
startGesture();
}
break;
}
case MotionEvent.ACTION_CANCEL: {
mNewPointerCount = 0;
stopGesture();
reset();
break;
}
}
return true;
}
/** Restarts the current gesture (if any). */
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 there is a gesture in progress */
public boolean isGestureInProgress() {
return mGestureInProgress;
}
/** Gets the number of pointers after the current gesture */
public int getNewPointerCount() {
return mNewPointerCount;
}
/** Gets the number of pointers in the current gesture */
public int getPointerCount() {
return mPointerCount;
}
/**
* 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;
}
}