diff options
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch')
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; |