summaryrefslogtreecommitdiff
path: root/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros')
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java161
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java185
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java180
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java213
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java223
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/package-info.java4
6 files changed, 966 insertions, 0 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java
new file mode 100644
index 0000000000..622e8f759c
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java
@@ -0,0 +1,161 @@
+package com.almeros.android.multitouch.gesturedetectors;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
+ * (code.almeros.com)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+public abstract class BaseGestureDetector {
+ protected final Context mContext;
+ protected boolean mGestureInProgress;
+
+ protected MotionEvent mPrevEvent;
+ protected MotionEvent mCurrEvent;
+
+ protected float mCurrPressure;
+ protected float mPrevPressure;
+ protected long mTimeDelta;
+
+ /**
+ * This value is the threshold ratio between the previous combined pressure
+ * and the current combined pressure. When pressure decreases rapidly
+ * between events the position values can often be imprecise, as it usually
+ * indicates that the user is in the process of lifting a pointer off of the
+ * device. This value was tuned experimentally.
+ */
+ protected static final float PRESSURE_THRESHOLD = 0.67f;
+
+ public BaseGestureDetector(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * All gesture detectors need to be called through this method to be able to
+ * detect gestures. This method delegates work to handler methods
+ * (handleStartProgressEvent, handleInProgressEvent) implemented in
+ * extending classes.
+ *
+ * @param event MotionEvent
+ * @return {@code true} as handled
+ */
+ public boolean onTouchEvent(MotionEvent event) {
+ final int actionCode = event.getAction() & MotionEvent.ACTION_MASK;
+ if (!mGestureInProgress) {
+ handleStartProgressEvent(actionCode, event);
+ } else {
+ handleInProgressEvent(actionCode, event);
+ }
+ return true;
+ }
+
+ /**
+ * Called when the current event occurred when NO gesture is in progress
+ * yet. The handling in this implementation may set the gesture in progress
+ * (via mGestureInProgress) or out of progress
+ *
+ * @param actionCode Action Code from MotionEvent
+ * @param event MotionEvent
+ */
+ protected abstract void handleStartProgressEvent(int actionCode,
+ MotionEvent event);
+
+ /**
+ * Called when the current event occurred when a gesture IS in progress. The
+ * handling in this implementation may set the gesture out of progress (via
+ * mGestureInProgress).
+ *
+ *
+ * @param actionCode Action Code from MotionEvent
+ * @param event MotionEvent
+ */
+ protected abstract void handleInProgressEvent(int actionCode,
+ MotionEvent event);
+
+ protected void updateStateByEvent(MotionEvent curr) {
+ final MotionEvent prev = mPrevEvent;
+
+ // Reset mCurrEvent
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ mCurrEvent = null;
+ }
+ mCurrEvent = MotionEvent.obtain(curr);
+
+ // Delta time
+ mTimeDelta = curr.getEventTime() - prev.getEventTime();
+
+ // Pressure
+ mCurrPressure = curr.getPressure(curr.getActionIndex());
+ mPrevPressure = prev.getPressure(prev.getActionIndex());
+ }
+
+ protected void resetState() {
+ if (mPrevEvent != null) {
+ mPrevEvent.recycle();
+ mPrevEvent = null;
+ }
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ mCurrEvent = null;
+ }
+ mGestureInProgress = false;
+ }
+
+ /**
+ * Returns {@code true} if a gesture is currently in progress.
+ *
+ * @return {@code true} if a gesture is currently in progress, {@code false}
+ * otherwise.
+ */
+ public boolean isInProgress() {
+ return mGestureInProgress;
+ }
+
+ /**
+ * Return the time difference in milliseconds between the previous accepted
+ * GestureDetector event and the current GestureDetector event.
+ *
+ * @return Time difference since the last move event in milliseconds.
+ */
+ public long getTimeDelta() {
+ return mTimeDelta;
+ }
+
+ /**
+ * Return the event time of the current GestureDetector event being
+ * processed.
+ *
+ * @return Current GestureDetector event time in milliseconds.
+ */
+ public long getEventTime() {
+ return mCurrEvent.getEventTime();
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java
new file mode 100644
index 0000000000..2430f3f920
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java
@@ -0,0 +1,185 @@
+package com.almeros.android.multitouch.gesturedetectors;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+/**
+ * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
+ * (code.almeros.com)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+public class MoveGestureDetector extends BaseGestureDetector {
+
+ /**
+ * Listener which must be implemented which is used by MoveGestureDetector
+ * to perform callbacks to any implementing class which is registered to a
+ * MoveGestureDetector via the constructor.
+ *
+ * @see MoveGestureDetector.SimpleOnMoveGestureListener
+ */
+ public interface OnMoveGestureListener {
+ public boolean onMove(MoveGestureDetector detector);
+
+ public boolean onMoveBegin(MoveGestureDetector detector);
+
+ public void onMoveEnd(MoveGestureDetector detector);
+ }
+
+ /**
+ * Helper class which may be extended and where the methods may be
+ * implemented. This way it is not necessary to implement all methods of
+ * OnMoveGestureListener.
+ */
+ public static class SimpleOnMoveGestureListener implements
+ OnMoveGestureListener {
+ public boolean onMove(MoveGestureDetector detector) {
+ return false;
+ }
+
+ public boolean onMoveBegin(MoveGestureDetector detector) {
+ return true;
+ }
+
+ public void onMoveEnd(MoveGestureDetector detector) {
+ // Do nothing, overridden implementation may be used
+ }
+ }
+
+ private static final PointF FOCUS_DELTA_ZERO = new PointF();
+
+ private final OnMoveGestureListener mListener;
+
+ private PointF mFocusExternal = new PointF();
+ private PointF mFocusDeltaExternal = new PointF();
+
+ public MoveGestureDetector(Context context, OnMoveGestureListener listener) {
+ super(context);
+ mListener = listener;
+ }
+
+ @Override
+ protected void handleStartProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_DOWN:
+ resetState(); // In case we missed an UP/CANCEL event
+
+ mPrevEvent = MotionEvent.obtain(event);
+ mTimeDelta = 0;
+
+ updateStateByEvent(event);
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ mGestureInProgress = mListener.onMoveBegin(this);
+ break;
+ }
+ }
+
+ @Override
+ protected void handleInProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mListener.onMoveEnd(this);
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ updateStateByEvent(event);
+
+ // Only accept the event if our relative pressure is within
+ // a certain limit. This can help filter shaky data as a
+ // finger is lifted.
+ if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
+ final boolean updatePrevious = mListener.onMove(this);
+ if (updatePrevious) {
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ }
+ }
+ break;
+ }
+ }
+
+ protected void updateStateByEvent(MotionEvent curr) {
+ super.updateStateByEvent(curr);
+
+ final MotionEvent prev = mPrevEvent;
+
+ // Focus intenal
+ PointF mCurrFocusInternal = determineFocalPoint(curr);
+ PointF mPrevFocusInternal = determineFocalPoint(prev);
+
+ // Focus external
+ // - Prevent skipping of focus delta when a finger is added or removed
+ boolean mSkipNextMoveEvent = prev.getPointerCount() != curr
+ .getPointerCount();
+ mFocusDeltaExternal = mSkipNextMoveEvent ? FOCUS_DELTA_ZERO
+ : new PointF(mCurrFocusInternal.x - mPrevFocusInternal.x,
+ mCurrFocusInternal.y - mPrevFocusInternal.y);
+
+ // - Don't directly use mFocusInternal (or skipping will occur). Add
+ // unskipped delta values to mFocusExternal instead.
+ mFocusExternal.x += mFocusDeltaExternal.x;
+ mFocusExternal.y += mFocusDeltaExternal.y;
+ }
+
+ /**
+ * Determine (multi)finger focal point (a.k.a. center point between all
+ * fingers)
+ *
+ * @param e
+ * @return PointF focal point
+ */
+ private PointF determineFocalPoint(MotionEvent e) {
+ // Number of fingers on screen
+ final int pCount = e.getPointerCount();
+ float x = 0.0f;
+ float y = 0.0f;
+
+ for (int i = 0; i < pCount; i++) {
+ x += e.getX(i);
+ y += e.getY(i);
+ }
+
+ return new PointF(x / pCount, y / pCount);
+ }
+
+ public float getFocusX() {
+ return mFocusExternal.x;
+ }
+
+ public float getFocusY() {
+ return mFocusExternal.y;
+ }
+
+ public PointF getFocusDelta() {
+ return mFocusDeltaExternal;
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java
new file mode 100644
index 0000000000..124fe8509c
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java
@@ -0,0 +1,180 @@
+package com.almeros.android.multitouch.gesturedetectors;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
+ * (code.almeros.com)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+public class RotateGestureDetector extends TwoFingerGestureDetector {
+
+ /**
+ * Listener which must be implemented which is used by RotateGestureDetector
+ * to perform callbacks to any implementing class which is registered to a
+ * RotateGestureDetector via the constructor.
+ *
+ * @see RotateGestureDetector.SimpleOnRotateGestureListener
+ */
+ public interface OnRotateGestureListener {
+ public boolean onRotate(RotateGestureDetector detector);
+
+ public boolean onRotateBegin(RotateGestureDetector detector);
+
+ public void onRotateEnd(RotateGestureDetector detector);
+ }
+
+ /**
+ * Helper class which may be extended and where the methods may be
+ * implemented. This way it is not necessary to implement all methods of
+ * OnRotateGestureListener.
+ */
+ public static class SimpleOnRotateGestureListener implements
+ OnRotateGestureListener {
+ public boolean onRotate(RotateGestureDetector detector) {
+ return false;
+ }
+
+ public boolean onRotateBegin(RotateGestureDetector detector) {
+ return true;
+ }
+
+ public void onRotateEnd(RotateGestureDetector detector) {
+ // Do nothing, overridden implementation may be used
+ }
+ }
+
+ private final OnRotateGestureListener mListener;
+ private boolean mSloppyGesture;
+
+ public RotateGestureDetector(Context context,
+ OnRotateGestureListener listener) {
+ super(context);
+ mListener = listener;
+ }
+
+ @Override
+ protected void handleStartProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // At least the second finger is on screen now
+
+ resetState(); // In case we missed an UP/CANCEL event
+ mPrevEvent = MotionEvent.obtain(event);
+ mTimeDelta = 0;
+
+ updateStateByEvent(event);
+
+ // See if we have a sloppy gesture
+ mSloppyGesture = isSloppyGesture(event);
+ if (!mSloppyGesture) {
+ // No, start gesture now
+ mGestureInProgress = mListener.onRotateBegin(this);
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (!mSloppyGesture) {
+ break;
+ }
+
+ // See if we still have a sloppy gesture
+ mSloppyGesture = isSloppyGesture(event);
+ if (!mSloppyGesture) {
+ // No, start normal gesture now
+ mGestureInProgress = mListener.onRotateBegin(this);
+ }
+
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ if (!mSloppyGesture) {
+ break;
+ }
+
+ break;
+ }
+ }
+
+ @Override
+ protected void handleInProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_POINTER_UP:
+ // Gesture ended but
+ updateStateByEvent(event);
+
+ if (!mSloppyGesture) {
+ mListener.onRotateEnd(this);
+ }
+
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (!mSloppyGesture) {
+ mListener.onRotateEnd(this);
+ }
+
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ updateStateByEvent(event);
+
+ // Only accept the event if our relative pressure is within
+ // a certain limit. This can help filter shaky data as a
+ // finger is lifted.
+ if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
+ final boolean updatePrevious = mListener.onRotate(this);
+ if (updatePrevious) {
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ }
+ }
+ break;
+ }
+ }
+
+ @Override
+ protected void resetState() {
+ super.resetState();
+ mSloppyGesture = false;
+ }
+
+ /**
+ * Return the rotation difference from the previous rotate event to the
+ * current event.
+ *
+ * @return The current rotation //difference in degrees.
+ */
+ public float getRotationDegreesDelta() {
+ double diffRadians = Math.atan2(mPrevFingerDiffY, mPrevFingerDiffX)
+ - Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX);
+ return (float) (diffRadians * 180.0 / Math.PI);
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java
new file mode 100644
index 0000000000..254597105b
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java
@@ -0,0 +1,213 @@
+package com.almeros.android.multitouch.gesturedetectors;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * @author Robert Nordan (robert.nordan@norkart.no)
+ *
+ * Copyright (c) 2013, Norkart AS
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+public class ShoveGestureDetector extends TwoFingerGestureDetector {
+
+ /**
+ * Listener which must be implemented which is used by ShoveGestureDetector
+ * to perform callbacks to any implementing class which is registered to a
+ * ShoveGestureDetector via the constructor.
+ *
+ * @see ShoveGestureDetector.SimpleOnShoveGestureListener
+ */
+ public interface OnShoveGestureListener {
+ public boolean onShove(ShoveGestureDetector detector);
+
+ public boolean onShoveBegin(ShoveGestureDetector detector);
+
+ public void onShoveEnd(ShoveGestureDetector detector);
+ }
+
+ /**
+ * Helper class which may be extended and where the methods may be
+ * implemented. This way it is not necessary to implement all methods of
+ * OnShoveGestureListener.
+ */
+ public static class SimpleOnShoveGestureListener implements
+ OnShoveGestureListener {
+ public boolean onShove(ShoveGestureDetector detector) {
+ return false;
+ }
+
+ public boolean onShoveBegin(ShoveGestureDetector detector) {
+ return true;
+ }
+
+ public void onShoveEnd(ShoveGestureDetector detector) {
+ // Do nothing, overridden implementation may be used
+ }
+ }
+
+ private float mPrevAverageY;
+ private float mCurrAverageY;
+
+ private final OnShoveGestureListener mListener;
+ private boolean mSloppyGesture;
+
+ public ShoveGestureDetector(Context context, OnShoveGestureListener listener) {
+ super(context);
+ mListener = listener;
+ }
+
+ @Override
+ protected void handleStartProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // At least the second finger is on screen now
+
+ resetState(); // In case we missed an UP/CANCEL event
+ mPrevEvent = MotionEvent.obtain(event);
+ mTimeDelta = 0;
+
+ updateStateByEvent(event);
+
+ // See if we have a sloppy gesture
+ mSloppyGesture = isSloppyGesture(event);
+ if (!mSloppyGesture) {
+ // No, start gesture now
+ mGestureInProgress = mListener.onShoveBegin(this);
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (!mSloppyGesture) {
+ break;
+ }
+
+ // See if we still have a sloppy gesture
+ mSloppyGesture = isSloppyGesture(event);
+ if (!mSloppyGesture) {
+ // No, start normal gesture now
+ mGestureInProgress = mListener.onShoveBegin(this);
+ }
+
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ if (!mSloppyGesture) {
+ break;
+ }
+
+ break;
+ }
+ }
+
+ @Override
+ protected void handleInProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_POINTER_UP:
+ // Gesture ended but
+ updateStateByEvent(event);
+
+ if (!mSloppyGesture) {
+ mListener.onShoveEnd(this);
+ }
+
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (!mSloppyGesture) {
+ mListener.onShoveEnd(this);
+ }
+
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ updateStateByEvent(event);
+
+ // Only accept the event if our relative pressure is within
+ // a certain limit. This can help filter shaky data as a
+ // finger is lifted. Also check that shove is meaningful.
+ if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD
+ && Math.abs(getShovePixelsDelta()) > 0.5f) {
+ final boolean updatePrevious = mListener.onShove(this);
+ if (updatePrevious) {
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ }
+ }
+ break;
+ }
+ }
+
+ @Override
+ protected void resetState() {
+ super.resetState();
+ mSloppyGesture = false;
+ mPrevAverageY = 0.0f;
+ mCurrAverageY = 0.0f;
+ }
+
+ @Override
+ protected void updateStateByEvent(MotionEvent curr) {
+ super.updateStateByEvent(curr);
+
+ final MotionEvent prev = mPrevEvent;
+ float py0 = prev.getY(0);
+ float py1 = prev.getY(1);
+ mPrevAverageY = (py0 + py1) / 2.0f;
+
+ float cy0 = curr.getY(0);
+ float cy1 = curr.getY(1);
+ mCurrAverageY = (cy0 + cy1) / 2.0f;
+ }
+
+ @Override
+ protected boolean isSloppyGesture(MotionEvent event) {
+ boolean sloppy = super.isSloppyGesture(event);
+ if (sloppy)
+ return true;
+
+ // If it's not traditionally sloppy, we check if the angle between
+ // fingers
+ // is acceptable.
+ double angle = Math.abs(Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX));
+ // about 20 degrees, left or right
+ return !((0.0f < angle && angle < 0.35f) || 2.79f < angle
+ && angle < Math.PI);
+ }
+
+ /**
+ * Return the distance in pixels from the previous shove event to the
+ * current event.
+ *
+ * @return The current distance in pixels.
+ */
+ public float getShovePixelsDelta() {
+ return mCurrAverageY - mPrevAverageY;
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java
new file mode 100644
index 0000000000..91e5ef13c8
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java
@@ -0,0 +1,223 @@
+package com.almeros.android.multitouch.gesturedetectors;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
+ * (code.almeros.com)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+public abstract class TwoFingerGestureDetector extends BaseGestureDetector {
+
+ private final float mEdgeSlop;
+
+ protected float mPrevFingerDiffX;
+ protected float mPrevFingerDiffY;
+ protected float mCurrFingerDiffX;
+ protected float mCurrFingerDiffY;
+
+ private float mCurrLen;
+ private float mPrevLen;
+
+ private PointF mFocus;
+
+ public TwoFingerGestureDetector(Context context) {
+ super(context);
+
+ ViewConfiguration config = ViewConfiguration.get(context);
+ mEdgeSlop = config.getScaledEdgeSlop();
+ }
+
+ @Override
+ protected abstract void handleStartProgressEvent(int actionCode,
+ MotionEvent event);
+
+ @Override
+ protected abstract void handleInProgressEvent(int actionCode,
+ MotionEvent event);
+
+ protected void updateStateByEvent(MotionEvent curr) {
+ super.updateStateByEvent(curr);
+
+ final MotionEvent prev = mPrevEvent;
+
+ mCurrLen = -1;
+ mPrevLen = -1;
+
+ // Previous
+ final float px0 = prev.getX(0);
+ final float py0 = prev.getY(0);
+ final float px1 = prev.getX(1);
+ final float py1 = prev.getY(1);
+ final float pvx = px1 - px0;
+ final float pvy = py1 - py0;
+ mPrevFingerDiffX = pvx;
+ mPrevFingerDiffY = pvy;
+
+ // Current
+ final float cx0 = curr.getX(0);
+ final float cy0 = curr.getY(0);
+ final float cx1 = curr.getX(1);
+ final float cy1 = curr.getY(1);
+ final float cvx = cx1 - cx0;
+ final float cvy = cy1 - cy0;
+ mCurrFingerDiffX = cvx;
+ mCurrFingerDiffY = cvy;
+ mFocus = determineFocalPoint(curr);
+ }
+
+ /**
+ * Return the current distance between the two pointers forming the gesture
+ * in progress.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public float getCurrentSpan() {
+ if (mCurrLen == -1) {
+ final float cvx = mCurrFingerDiffX;
+ final float cvy = mCurrFingerDiffY;
+ mCurrLen = (float) Math.sqrt(cvx * cvx + cvy * cvy);
+ }
+ return mCurrLen;
+ }
+
+ /**
+ * Return the previous distance between the two pointers forming the gesture
+ * in progress.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public float getPreviousSpan() {
+ if (mPrevLen == -1) {
+ final float pvx = mPrevFingerDiffX;
+ final float pvy = mPrevFingerDiffY;
+ mPrevLen = (float) Math.sqrt(pvx * pvx + pvy * pvy);
+ }
+ return mPrevLen;
+ }
+
+ /**
+ * MotionEvent has no getRawX(int) method; simulate it pending future API
+ * approval.
+ *
+ * @param event Motion Event
+ * @param pointerIndex Pointer Index
+ * @return Raw x value or 0
+ */
+ protected static float getRawX(MotionEvent event, int pointerIndex) {
+ float offset = event.getX() - event.getRawX();
+ if (pointerIndex < event.getPointerCount()) {
+ return event.getX(pointerIndex) + offset;
+ }
+ return 0.0f;
+ }
+
+ /**
+ * MotionEvent has no getRawY(int) method; simulate it pending future API
+ * approval.
+ *
+ * @param event Motion Event
+ * @param pointerIndex Pointer Index
+ * @return Raw y value or 0
+ */
+ protected static float getRawY(MotionEvent event, int pointerIndex) {
+ float offset = event.getY() - event.getRawY();
+ if (pointerIndex < event.getPointerCount()) {
+ return event.getY(pointerIndex) + offset;
+ }
+ return 0.0f;
+ }
+
+ /**
+ * Check if we have a sloppy gesture. Sloppy gestures can happen if the edge
+ * of the user's hand is touching the screen, for example.
+ *
+ * @param event Motion Event
+ * @return {@code true} if is sloppy gesture, {@code false} if not
+ */
+ protected boolean isSloppyGesture(MotionEvent event) {
+ // As orientation can change, query the metrics in touch down
+ DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
+ float mRightSlopEdge = metrics.widthPixels - mEdgeSlop;
+ float mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;
+
+ final float edgeSlop = mEdgeSlop;
+
+ final float x0 = event.getRawX();
+ final float y0 = event.getRawY();
+ final float x1 = getRawX(event, 1);
+ final float y1 = getRawY(event, 1);
+
+ boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop || x0 > mRightSlopEdge
+ || y0 > mBottomSlopEdge;
+ boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop || x1 > mRightSlopEdge
+ || y1 > mBottomSlopEdge;
+
+ if (p0sloppy && p1sloppy) {
+ return true;
+ } else if (p0sloppy) {
+ return true;
+ } else if (p1sloppy) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Determine (multi)finger focal point (a.k.a. center point between all
+ * fingers)
+ *
+ * @param e Motion Event
+ * @return PointF focal point
+ */
+ public static PointF determineFocalPoint(MotionEvent e) {
+ // Number of fingers on screen
+ final int pCount = e.getPointerCount();
+ float x = 0.0f;
+ float y = 0.0f;
+
+ for (int i = 0; i < pCount; i++) {
+ x += e.getX(i);
+ y += e.getY(i);
+ }
+
+ return new PointF(x / pCount, y / pCount);
+ }
+
+ public float getFocusX() {
+ return mFocus.x;
+ }
+
+ public float getFocusY() {
+ return mFocus.y;
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/package-info.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/package-info.java
new file mode 100644
index 0000000000..abc0a11892
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Do not use this package. Used internally by the SDK.
+ */
+package com.almeros.android.multitouch.gesturedetectors;