summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorŁukasz Paczos <lukasz.paczos@mapbox.com>2018-02-27 15:51:22 +0100
committerŁukasz Paczos <lukas.paczos@gmail.com>2018-02-27 15:57:17 +0100
commit3b74449c8e2ecd84120319efc4528036eba77a7b (patch)
tree43a317645a7240b866bc3985d138f21d95772585
parent317794746e51ad801c9b53f987fc602acc533cc3 (diff)
downloadqtlocation-mapboxgl-upstream/lp-cp-11221.tar.gz
[android ] - new gestures library (#11221)upstream/lp-cp-11221
* [android] new gesture library - added SNAPSHOT dependency * [android] new gesture library - cleaned up redundant classes * [android] new gesture library - limiting scale when rotating * [android] new gesture library - shove gesture filtering * [android] new gesture library - increase rotation threshold when scaling * [android] new gesture library - minimum angular velocity * [android] new gesture library - exposed gestures execution listeners * [android] new gesture library - notifying new listeners tests * [android] new gesture library - removed tracking setting * [android] new gesture library - resetting focal point with every scale/rotate callback * [android] new gesture library - fixed camera change dispatcher callbacks * [android] new gesture library - cancel velocity animations in maps onStop() * [android] new gesture library - extracted telemetry pushes to a method * [android] new gesture library - deprecated onScrollListener * [android] new gesture library - unified shove listener name (cherry picked from commit 7905bd6)
-rw-r--r--platform/android/MapboxGLAndroidSDK/build.gradle1
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java160
-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.java214
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java224
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/package-info.java4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java29
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java1096
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapKeyListener.java8
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java73
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java202
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java34
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml9
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapTouchListenersTest.java132
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java2
-rw-r--r--platform/android/gradle/dependencies.gradle24
17 files changed, 980 insertions, 1597 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/build.gradle b/platform/android/MapboxGLAndroidSDK/build.gradle
index 173b4fa7f8..94b816c1ab 100644
--- a/platform/android/MapboxGLAndroidSDK/build.gradle
+++ b/platform/android/MapboxGLAndroidSDK/build.gradle
@@ -3,6 +3,7 @@ apply plugin: 'com.android.library'
dependencies {
api dependenciesList.mapboxAndroidTelemetry
api dependenciesList.mapboxJavaGeoJSON
+ api dependenciesList.mapboxAndroidGestures
implementation dependenciesList.supportAnnotations
implementation dependenciesList.supportFragmentV4
implementation dependenciesList.timber
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
deleted file mode 100644
index b7bcb925a1..0000000000
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java
+++ /dev/null
@@ -1,160 +0,0 @@
-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)
- * <p>
- * All rights reserved.
- * <p>
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * <p>
- * 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.
- * <p>
- * 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 context;
- protected boolean gestureInProgress;
-
- protected MotionEvent prevEvent;
- protected MotionEvent currEvent;
-
- protected float currPressure;
- protected float prevPressure;
- protected long timeDelta;
-
- /**
- * 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) {
- this.context = 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 (!gestureInProgress) {
- 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 gestureInProgress) 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
- * gestureInProgress).
- *
- * @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 = prevEvent;
-
- // Reset currEvent
- if (currEvent != null) {
- currEvent.recycle();
- currEvent = null;
- }
- currEvent = MotionEvent.obtain(curr);
-
- // Delta time
- timeDelta = curr.getEventTime() - prev.getEventTime();
-
- // Pressure
- currPressure = curr.getPressure(curr.getActionIndex());
- prevPressure = prev.getPressure(prev.getActionIndex());
- }
-
- protected void resetState() {
- if (prevEvent != null) {
- prevEvent.recycle();
- prevEvent = null;
- }
- if (currEvent != null) {
- currEvent.recycle();
- currEvent = null;
- }
- gestureInProgress = 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 gestureInProgress;
- }
-
- /**
- * 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 timeDelta;
- }
-
- /**
- * Return the event time of the current GestureDetector event being
- * processed.
- *
- * @return Current GestureDetector event time in milliseconds.
- */
- public long getEventTime() {
- return currEvent.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
deleted file mode 100644
index bc7dda6159..0000000000
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java
+++ /dev/null
@@ -1,185 +0,0 @@
-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)
- * <p>
- * All rights reserved.
- * <p>
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * <p>
- * 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.
- * <p>
- * 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 listener;
-
- private PointF focusExternal = new PointF();
- private PointF focusDeltaExternal = new PointF();
-
- public MoveGestureDetector(Context context, OnMoveGestureListener listener) {
- super(context);
- this.listener = listener;
- }
-
- @Override
- protected void handleStartProgressEvent(int actionCode, MotionEvent event) {
- switch (actionCode) {
- case MotionEvent.ACTION_DOWN:
- resetState(); // In case we missed an UP/CANCEL event
-
- prevEvent = MotionEvent.obtain(event);
- timeDelta = 0;
-
- updateStateByEvent(event);
- break;
-
- case MotionEvent.ACTION_MOVE:
- gestureInProgress = listener.onMoveBegin(this);
- break;
- }
- }
-
- @Override
- protected void handleInProgressEvent(int actionCode, MotionEvent event) {
- switch (actionCode) {
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- listener.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 (currPressure / prevPressure > PRESSURE_THRESHOLD) {
- final boolean updatePrevious = listener.onMove(this);
- if (updatePrevious) {
- prevEvent.recycle();
- prevEvent = MotionEvent.obtain(event);
- }
- }
- break;
- }
- }
-
- protected void updateStateByEvent(MotionEvent curr) {
- super.updateStateByEvent(curr);
-
- final MotionEvent prev = prevEvent;
-
- // Focus intenal
- PointF currFocusInternal = determineFocalPoint(curr);
- PointF prevFocusInternal = determineFocalPoint(prev);
-
- // Focus external
- // - Prevent skipping of focus delta when a finger is added or removed
- boolean skipNextMoveEvent = prev.getPointerCount() != curr
- .getPointerCount();
- focusDeltaExternal = skipNextMoveEvent ? FOCUS_DELTA_ZERO
- : new PointF(currFocusInternal.x - prevFocusInternal.x,
- currFocusInternal.y - prevFocusInternal.y);
-
- // - Don't directly use mFocusInternal (or skipping will occur). Add
- // unskipped delta values to focusExternal instead.
- focusExternal.x += focusDeltaExternal.x;
- focusExternal.y += focusDeltaExternal.y;
- }
-
- /**
- * Determine (multi)finger focal point (a.k.a. center point between all
- * fingers)
- *
- * @param motionEvent a {@link MotionEvent} object.
- * @return PointF focal point
- */
- private PointF determineFocalPoint(MotionEvent motionEvent) {
- // Number of fingers on screen
- final int pCount = motionEvent.getPointerCount();
- float x = 0.0f;
- float y = 0.0f;
-
- for (int i = 0; i < pCount; i++) {
- x += motionEvent.getX(i);
- y += motionEvent.getY(i);
- }
-
- return new PointF(x / pCount, y / pCount);
- }
-
- public float getFocusX() {
- return focusExternal.x;
- }
-
- public float getFocusY() {
- return focusExternal.y;
- }
-
- public PointF getFocusDelta() {
- return focusDeltaExternal;
- }
-
-}
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
deleted file mode 100644
index 8c111a68df..0000000000
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java
+++ /dev/null
@@ -1,180 +0,0 @@
-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)
- * <p>
- * All rights reserved.
- * <p>
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * <p>
- * 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.
- * <p>
- * 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 listener;
- private boolean sloppyGesture;
-
- public RotateGestureDetector(Context context,
- OnRotateGestureListener listener) {
- super(context);
- this.listener = 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
- prevEvent = MotionEvent.obtain(event);
- timeDelta = 0;
-
- updateStateByEvent(event);
-
- // See if we have a sloppy gesture
- sloppyGesture = isSloppyGesture(event);
- if (!sloppyGesture) {
- // No, start gesture now
- gestureInProgress = listener.onRotateBegin(this);
- }
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (!sloppyGesture) {
- break;
- }
-
- // See if we still have a sloppy gesture
- sloppyGesture = isSloppyGesture(event);
- if (!sloppyGesture) {
- // No, start normal gesture now
- gestureInProgress = listener.onRotateBegin(this);
- }
-
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- if (!sloppyGesture) {
- break;
- }
-
- break;
- }
- }
-
- @Override
- protected void handleInProgressEvent(int actionCode, MotionEvent event) {
- switch (actionCode) {
- case MotionEvent.ACTION_POINTER_UP:
- // Gesture ended but
- updateStateByEvent(event);
-
- if (!sloppyGesture) {
- listener.onRotateEnd(this);
- }
-
- resetState();
- break;
-
- case MotionEvent.ACTION_CANCEL:
- if (!sloppyGesture) {
- listener.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 (currPressure / prevPressure > PRESSURE_THRESHOLD) {
- final boolean updatePrevious = listener.onRotate(this);
- if (updatePrevious) {
- prevEvent.recycle();
- prevEvent = MotionEvent.obtain(event);
- }
- }
- break;
- }
- }
-
- @Override
- protected void resetState() {
- super.resetState();
- sloppyGesture = 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(prevFingerDiffY, prevFingerDiffX)
- - Math.atan2(currFingerDiffY, currFingerDiffX);
- 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
deleted file mode 100644
index 9396578e48..0000000000
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java
+++ /dev/null
@@ -1,214 +0,0 @@
-package com.almeros.android.multitouch.gesturedetectors;
-
-import android.content.Context;
-import android.view.MotionEvent;
-
-/**
- * @author Robert Nordan (robert.nordan@norkart.no)
- * <p>
- * Copyright (c) 2013, Norkart AS
- * <p>
- * All rights reserved.
- * <p>
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * <p>
- * 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.
- * <p>
- * 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 prevAverageY;
- private float currAverageY;
-
- private final OnShoveGestureListener listener;
- private boolean sloppyGesture;
-
- public ShoveGestureDetector(Context context, OnShoveGestureListener listener) {
- super(context);
- this.listener = 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
- prevEvent = MotionEvent.obtain(event);
- timeDelta = 0;
-
- updateStateByEvent(event);
-
- // See if we have a sloppy gesture
- sloppyGesture = isSloppyGesture(event);
- if (!sloppyGesture) {
- // No, start gesture now
- gestureInProgress = listener.onShoveBegin(this);
- }
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (!sloppyGesture) {
- break;
- }
-
- // See if we still have a sloppy gesture
- sloppyGesture = isSloppyGesture(event);
- if (!sloppyGesture) {
- // No, start normal gesture now
- gestureInProgress = listener.onShoveBegin(this);
- }
-
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- if (!sloppyGesture) {
- break;
- }
-
- break;
- }
- }
-
- @Override
- protected void handleInProgressEvent(int actionCode, MotionEvent event) {
- switch (actionCode) {
- case MotionEvent.ACTION_POINTER_UP:
- // Gesture ended but
- updateStateByEvent(event);
-
- if (!sloppyGesture) {
- listener.onShoveEnd(this);
- }
-
- resetState();
- break;
-
- case MotionEvent.ACTION_CANCEL:
- if (!sloppyGesture) {
- listener.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 (currPressure / prevPressure > PRESSURE_THRESHOLD
- && Math.abs(getShovePixelsDelta()) > 0.5f) {
- final boolean updatePrevious = listener.onShove(this);
- if (updatePrevious) {
- prevEvent.recycle();
- prevEvent = MotionEvent.obtain(event);
- }
- }
- break;
- }
- }
-
- @Override
- protected void resetState() {
- super.resetState();
- sloppyGesture = false;
- prevAverageY = 0.0f;
- currAverageY = 0.0f;
- }
-
- @Override
- protected void updateStateByEvent(MotionEvent curr) {
- super.updateStateByEvent(curr);
-
- final MotionEvent prev = prevEvent;
- float py0 = prev.getY(0);
- float py1 = prev.getY(1);
- prevAverageY = (py0 + py1) / 2.0f;
-
- float cy0 = curr.getY(0);
- float cy1 = curr.getY(1);
- currAverageY = (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(currFingerDiffY, currFingerDiffX));
- // 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 currAverageY - prevAverageY;
- }
-}
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
deleted file mode 100644
index db492b6556..0000000000
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java
+++ /dev/null
@@ -1,224 +0,0 @@
-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)
- * <p>
- * All rights reserved.
- * <p>
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * <p>
- * 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.
- * <p>
- * 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 edgeSlop;
-
- protected float prevFingerDiffX;
- protected float prevFingerDiffY;
- protected float currFingerDiffX;
- protected float currFingerDiffY;
-
- private float currLen;
- private float prevLen;
-
- private PointF focus;
-
- public TwoFingerGestureDetector(Context context) {
- super(context);
-
- ViewConfiguration config = ViewConfiguration.get(context);
-
- edgeSlop = 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 = prevEvent;
-
- currLen = -1;
- prevLen = -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;
- prevFingerDiffX = pvx;
- prevFingerDiffY = 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;
- currFingerDiffX = cvx;
- currFingerDiffY = cvy;
- focus = 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 (currLen == -1) {
- final float cvx = currFingerDiffX;
- final float cvy = currFingerDiffY;
- currLen = (float) Math.sqrt(cvx * cvx + cvy * cvy);
- }
- return currLen;
- }
-
- /**
- * Return the previous distance between the two pointers forming the gesture
- * in progress.
- *
- * @return Previous distance between pointers in pixels.
- */
- public float getPreviousSpan() {
- if (prevLen == -1) {
- final float pvx = prevFingerDiffX;
- final float pvy = prevFingerDiffY;
- prevLen = (float) Math.sqrt(pvx * pvx + pvy * pvy);
- }
- return prevLen;
- }
-
- /**
- * 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.getRawX() - event.getX();
- 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.getRawY() - event.getY();
- 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 = context.getResources().getDisplayMetrics();
- float rightSlopEdge = metrics.widthPixels - edgeSlop;
- float bottomSlopEdge = metrics.heightPixels - edgeSlop;
-
- final float edgeSlop = this.edgeSlop;
-
- 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 > rightSlopEdge
- || y0 > bottomSlopEdge;
- boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop || x1 > rightSlopEdge
- || y1 > bottomSlopEdge;
-
- 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 motionEvent Motion Event
- * @return PointF focal point
- */
- public static PointF determineFocalPoint(MotionEvent motionEvent) {
- // Number of fingers on screen
- final int pCount = motionEvent.getPointerCount();
- float x = 0.0f;
- float y = 0.0f;
-
- for (int i = 0; i < pCount; i++) {
- x += motionEvent.getX(i);
- y += motionEvent.getY(i);
- }
-
- return new PointF(x / pCount, y / pCount);
- }
-
- public float getFocusX() {
- return focus.x;
- }
-
- public float getFocusY() {
- return focus.y;
- }
-
-} \ No newline at end of file
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
deleted file mode 100644
index cff2f086dc..0000000000
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * Do not use this package. Internal use only.
- */
-package com.almeros.android.multitouch.gesturedetectors;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java
index 60362dd2e9..6f263e4635 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java
@@ -48,6 +48,31 @@ public class MapboxConstants {
public static final long VELOCITY_THRESHOLD_IGNORE_FLING = 1000;
/**
+ * Value by which the default rotation threshold will be increased when scaling
+ */
+ public static final float ROTATION_THRESHOLD_INCREASE_WHEN_SCALING = 25f;
+
+ /**
+ * Time within which user needs to lift fingers for velocity animation to start.
+ */
+ public static final long SCHEDULED_ANIMATION_TIMEOUT = 150L;
+
+ /**
+ * Minimum angular velocity for rotation animation
+ */
+ public static final float MINIMUM_ANGULAR_VELOCITY = 1.5f;
+
+ /**
+ * Maximum angular velocity for rotation animation
+ */
+ public static final float MAXIMUM_ANGULAR_VELOCITY = 20f;
+
+ /**
+ * Factor to calculate tilt change based on pixel change during shove gesture.
+ */
+ public static final float SHOVE_PIXEL_CHANGE_FACTOR = 0.1f;
+
+ /**
* The currently supported minimum zoom level.
*/
public static final float MINIMUM_ZOOM = 0.0f;
@@ -78,14 +103,14 @@ public class MapboxConstants {
public static final double MINIMUM_DIRECTION = 0;
/**
- * The currently used minimun scale factor to clamp to when a quick zoom gesture occurs
+ * The currently used minimum scale factor to clamp to when a quick zoom gesture occurs
*/
public static final float MINIMUM_SCALE_FACTOR_CLAMP = 0.00f;
/**
* The currently used maximum scale factor to clamp to when a quick zoom gesture occurs
*/
- public static final float MAXIMUM_SCALE_FACTOR_CLAMP = 0.45f;
+ public static final float MAXIMUM_SCALE_FACTOR_CLAMP = 0.15f;
/**
* Fragment Argument Key for MapboxMapOptions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java
index 8047e19809..5f5a10d0d0 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java
@@ -5,26 +5,31 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.PointF;
+import android.os.Handler;
import android.support.annotation.Nullable;
-import android.support.v4.view.GestureDetectorCompat;
-import android.support.v4.view.ScaleGestureDetectorCompat;
-import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.view.InputDevice;
import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.VelocityTracker;
-import android.view.ViewConfiguration;
-
-import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector;
-import com.almeros.android.multitouch.gesturedetectors.ShoveGestureDetector;
-import com.almeros.android.multitouch.gesturedetectors.TwoFingerGestureDetector;
+import android.view.animation.DecelerateInterpolator;
+
+import com.mapbox.android.gestures.AndroidGesturesManager;
+import com.mapbox.android.gestures.MoveGestureDetector;
+import com.mapbox.android.gestures.MultiFingerTapGestureDetector;
+import com.mapbox.android.gestures.RotateGestureDetector;
+import com.mapbox.android.gestures.ShoveGestureDetector;
+import com.mapbox.android.gestures.StandardGestureDetector;
+import com.mapbox.android.gestures.StandardScaleGestureDetector;
import com.mapbox.android.telemetry.Event;
import com.mapbox.android.telemetry.MapEventFactory;
import com.mapbox.android.telemetry.MapState;
+import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.utils.MathUtils;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener.REASON_API_ANIMATION;
@@ -32,24 +37,15 @@ import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener.RE
/**
* Manages gestures events on a MapView.
- * <p>
- * Relies on gesture detection code in almeros.android.multitouch.gesturedetectors.
- * </p>
*/
final class MapGestureDetector {
private final Transform transform;
private final Projection projection;
private final UiSettings uiSettings;
- private final TrackingSettings trackingSettings;
private final AnnotationManager annotationManager;
private final CameraChangeDispatcher cameraChangeDispatcher;
- private GestureDetectorCompat gestureDetector;
- private ScaleGestureDetector scaleGestureDetector;
- private RotateGestureDetector rotateGestureDetector;
- private ShoveGestureDetector shoveGestureDetector;
-
// deprecated map touch API
private MapboxMap.OnMapClickListener onMapClickListener;
private MapboxMap.OnMapLongClickListener onMapLongClickListener;
@@ -69,43 +65,73 @@ final class MapGestureDetector {
private final CopyOnWriteArrayList<MapboxMap.OnScrollListener> onScrollListenerList
= new CopyOnWriteArrayList<>();
- private PointF focalPoint;
+ private final CopyOnWriteArrayList<MapboxMap.OnMoveListener> onMoveListenerList
+ = new CopyOnWriteArrayList<>();
- private boolean twoTap;
- private boolean quickZoom;
- private boolean tiltGestureOccurred;
- private boolean scrollGestureOccurred;
+ private final CopyOnWriteArrayList<MapboxMap.OnRotateListener> onRotateListenerList
+ = new CopyOnWriteArrayList<>();
+
+ private final CopyOnWriteArrayList<MapboxMap.OnScaleListener> onScaleListenerList
+ = new CopyOnWriteArrayList<>();
+
+ private final CopyOnWriteArrayList<MapboxMap.OnShoveListener> onShoveListenerList
+ = new CopyOnWriteArrayList<>();
+
+ /**
+ * User-set focal point.
+ */
+ private PointF focalPoint;
- private boolean scaleGestureOccurred;
- private boolean recentScaleGestureOccurred;
- private long scaleBeginTime;
+ private AndroidGesturesManager gesturesManager;
+ private boolean executeDoubleTap;
- private boolean scaleAnimating;
- private boolean rotateAnimating;
+ private Animator scaleAnimator;
+ private Animator rotateAnimator;
+ private final List<Animator> scheduledAnimators = new ArrayList<>();
- private VelocityTracker velocityTracker;
- private boolean wasZoomingIn;
- private boolean wasClockwiseRotating;
- private boolean rotateGestureOccurred;
+ /**
+ * Cancels scheduled velocity animations if user doesn't lift fingers within
+ * {@link MapboxConstants#SCHEDULED_ANIMATION_TIMEOUT}
+ */
+ private Handler animationsTimeoutHandler = new Handler();
MapGestureDetector(Context context, Transform transform, Projection projection, UiSettings uiSettings,
- TrackingSettings trackingSettings, AnnotationManager annotationManager,
- CameraChangeDispatcher cameraChangeDispatcher) {
+ AnnotationManager annotationManager, CameraChangeDispatcher cameraChangeDispatcher) {
this.annotationManager = annotationManager;
this.transform = transform;
this.projection = projection;
this.uiSettings = uiSettings;
- this.trackingSettings = trackingSettings;
this.cameraChangeDispatcher = cameraChangeDispatcher;
- // Touch gesture detectors
+ // Checking for context != null for testing purposes
if (context != null) {
- gestureDetector = new GestureDetectorCompat(context, new GestureListener());
- gestureDetector.setIsLongpressEnabled(true);
- scaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener());
- ScaleGestureDetectorCompat.setQuickScaleEnabled(scaleGestureDetector, true);
- rotateGestureDetector = new RotateGestureDetector(context, new RotateGestureListener());
- shoveGestureDetector = new ShoveGestureDetector(context, new ShoveGestureListener());
+ gesturesManager = new AndroidGesturesManager(context);
+
+ Set<Integer> shoveScaleSet = new HashSet<>();
+ shoveScaleSet.add(AndroidGesturesManager.GESTURE_TYPE_SHOVE);
+ shoveScaleSet.add(AndroidGesturesManager.GESTURE_TYPE_SCALE);
+
+ Set<Integer> shoveRotateSet = new HashSet<>();
+ shoveRotateSet.add(AndroidGesturesManager.GESTURE_TYPE_SHOVE);
+ shoveRotateSet.add(AndroidGesturesManager.GESTURE_TYPE_ROTATE);
+
+ Set<Integer> ScaleLongPressSet = new HashSet<>();
+ ScaleLongPressSet.add(AndroidGesturesManager.GESTURE_TYPE_SCALE);
+ ScaleLongPressSet.add(AndroidGesturesManager.GESTURE_TYPE_LONG_PRESS);
+
+ gesturesManager.setMutuallyExclusiveGestures(shoveScaleSet, shoveRotateSet, ScaleLongPressSet);
+
+ gesturesManager.setStandardGestureListener(new StandardGestureListener());
+ gesturesManager.setMoveGestureListener(new MoveGestureListener());
+ gesturesManager.setStandardScaleGestureListener(new ScaleGestureListener(
+ context.getResources().getDimension(R.dimen.mapbox_minimum_scale_velocity)
+ ));
+ gesturesManager.setRotateGestureListener(new RotateGestureListener(
+ context.getResources().getDimension(R.dimen.mapbox_minimum_scale_span_when_rotating),
+ context.getResources().getDimension(R.dimen.mapbox_minimum_angular_velocity)
+ ));
+ gesturesManager.setShoveGestureListener(new ShoveGestureListener());
+ gesturesManager.setMultiFingerTapGestureListener(new TapGestureListener());
}
}
@@ -132,8 +158,9 @@ final class MapGestureDetector {
/**
* Get the current active gesture focal point.
* <p>
- * This could be either the user provided focal point in {@link UiSettings#setFocalPoint(PointF)} or the focal point
- * defined as a result of {@link TrackingSettings#setMyLocationEnabled(boolean)}.
+ * This could be either the user provided focal point in
+ * {@link UiSettings#setFocalPoint(PointF)}or <code>null</code>.
+ * If it's <code>null</code>, gestures will use focal pointed returned by the detector.
* </p>
*
* @return the current active gesture focal point.
@@ -149,118 +176,79 @@ final class MapGestureDetector {
* Forwards event to the related gesture detectors.
* </p>
*
- * @param event the MotionEvent
+ * @param motionEvent the MotionEvent
* @return True if touch event is handled
*/
- boolean onTouchEvent(MotionEvent event) {
- // framework can return null motion events in edge cases #9432
- if (event == null) {
+ boolean onTouchEvent(MotionEvent motionEvent) {
+ // Framework can return null motion events in edge cases #9432
+ if (motionEvent == null) {
return false;
}
// Check and ignore non touch or left clicks
- if ((event.getButtonState() != 0) && (event.getButtonState() != MotionEvent.BUTTON_PRIMARY)) {
+ if ((motionEvent.getButtonState() != 0) && (motionEvent.getButtonState() != MotionEvent.BUTTON_PRIMARY)) {
return false;
}
- // Check two finger gestures first
- scaleGestureDetector.onTouchEvent(event);
- rotateGestureDetector.onTouchEvent(event);
- shoveGestureDetector.onTouchEvent(event);
+ boolean result = gesturesManager.onTouchEvent(motionEvent);
- // Handle two finger tap
- switch (event.getActionMasked()) {
+ switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
- if (velocityTracker == null) {
- velocityTracker = VelocityTracker.obtain();
- } else {
- velocityTracker.clear();
- }
- velocityTracker.addMovement(event);
- // First pointer down, reset scaleGestureOccurred, used to avoid triggering a fling after a scale gesture #7666
- recentScaleGestureOccurred = false;
+ cancelAnimators();
transform.setGestureInProgress(true);
break;
+ case MotionEvent.ACTION_UP:
+ transform.setGestureInProgress(false);
- case MotionEvent.ACTION_POINTER_DOWN:
- // Second pointer down
- twoTap = event.getPointerCount() == 2
- && uiSettings.isZoomGesturesEnabled();
- if (twoTap) {
- // Confirmed 2nd Finger Down
- if (isZoomValid(transform)) {
- MapEventFactory mapEventFactory = new MapEventFactory();
- LatLng latLng = projection.fromScreenLocation(new PointF(event.getX(), event.getY()));
- MapState twoFingerTap = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom());
- twoFingerTap.setGesture(Events.TWO_FINGER_TAP);
- Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, twoFingerTap));
- }
+ // Start all awaiting velocity animations
+ animationsTimeoutHandler.removeCallbacksAndMessages(null);
+ for (Animator animator : scheduledAnimators) {
+ animator.start();
}
+ scheduledAnimators.clear();
break;
- case MotionEvent.ACTION_POINTER_UP:
- // Second pointer up
+ case MotionEvent.ACTION_CANCEL:
+ scheduledAnimators.clear();
+ transform.setGestureInProgress(false);
break;
+ }
- case MotionEvent.ACTION_UP:
- // First pointer up
- long tapInterval = event.getEventTime() - event.getDownTime();
- boolean isTap = tapInterval <= ViewConfiguration.getTapTimeout();
- boolean inProgress = rotateGestureDetector.isInProgress()
- || scaleGestureDetector.isInProgress()
- || shoveGestureDetector.isInProgress();
-
- if (twoTap && isTap && !inProgress) {
- if (focalPoint != null) {
- transform.zoom(false, focalPoint);
- } else {
- PointF focalPoint = TwoFingerGestureDetector.determineFocalPoint(event);
- transform.zoom(false, focalPoint);
- }
- twoTap = false;
- return true;
- }
-
- // Scroll / Pan Has Stopped
- if (scrollGestureOccurred) {
- if (isZoomValid(transform)) {
- MapEventFactory mapEventFactory = new MapEventFactory();
- LatLng latLng = projection.fromScreenLocation(new PointF(event.getX(), event.getY()));
- MapState dragend = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom());
- Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_DRAGEND, dragend));
- }
- scrollGestureOccurred = false;
+ return result;
+ }
- if (!scaleAnimating && !rotateAnimating) {
- cameraChangeDispatcher.onCameraIdle();
- }
- }
+ void cancelAnimators() {
+ if (scaleAnimator != null) {
+ scaleAnimator.cancel();
+ }
+ if (rotateAnimator != null) {
+ rotateAnimator.cancel();
+ }
- twoTap = false;
- transform.setGestureInProgress(false);
- if (velocityTracker != null) {
- velocityTracker.recycle();
- }
- velocityTracker = null;
- break;
+ animationsTimeoutHandler.removeCallbacksAndMessages(null);
+ scheduledAnimators.clear();
+ }
- case MotionEvent.ACTION_CANCEL:
- twoTap = false;
- transform.setGestureInProgress(false);
- if (velocityTracker != null) {
- velocityTracker.recycle();
- }
- velocityTracker = null;
- break;
- case MotionEvent.ACTION_MOVE:
- if (velocityTracker != null) {
- velocityTracker.addMovement(event);
- velocityTracker.computeCurrentVelocity(1000);
- }
- break;
+ /**
+ * Posted on main thread with {@link #animationsTimeoutHandler}. Cancels all scheduled animators if needed.
+ */
+ private Runnable cancelAnimatorsRunnable = new Runnable() {
+ @Override
+ public void run() {
+ cancelAnimators();
}
+ };
- return gestureDetector.onTouchEvent(event);
+ /**
+ * Schedules a velocity animator to be executed when user lift fingers,
+ * unless canceled by the {@link #cancelAnimatorsRunnable}.
+ *
+ * @param animator animator ot be scheduled
+ */
+ private void scheduleAnimator(Animator animator) {
+ scheduledAnimators.add(animator);
+ animationsTimeoutHandler.removeCallbacksAndMessages(null);
+ animationsTimeoutHandler.postDelayed(cancelAnimatorsRunnable, MapboxConstants.SCHEDULED_ANIMATION_TIMEOUT);
}
/**
@@ -269,7 +257,7 @@ final class MapGestureDetector {
* Examples of such events are mouse scroll events, mouse moves, joystick & trackpad.
* </p>
*
- * @param event The MotionEvent occured
+ * @param event The MotionEvent occurred
* @return True is the event is handled
*/
boolean onGenericMotionEvent(MotionEvent event) {
@@ -291,7 +279,7 @@ final class MapGestureDetector {
float scrollDist = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
// Scale the map by the appropriate power of two factor
- transform.zoomBy(scrollDist, event.getX(), event.getY());
+ transform.zoomBy(scrollDist, new PointF(event.getX(), event.getY()));
return true;
@@ -305,61 +293,14 @@ final class MapGestureDetector {
return false;
}
- /**
- * Responsible for handling one finger gestures.
- */
- private class GestureListener extends android.view.GestureDetector.SimpleOnGestureListener {
-
+ private final class StandardGestureListener extends StandardGestureDetector.SimpleStandardOnGestureListener {
@Override
- public boolean onDown(MotionEvent event) {
- return true;
- }
-
- @Override
- public boolean onDoubleTapEvent(MotionEvent e) {
- if (!uiSettings.isZoomGesturesEnabled() || !uiSettings.isDoubleTapGesturesEnabled()) {
- return false;
- }
-
- switch (e.getAction()) {
- case MotionEvent.ACTION_DOWN:
- break;
- case MotionEvent.ACTION_MOVE:
- break;
- case MotionEvent.ACTION_UP:
- if (quickZoom) {
- cameraChangeDispatcher.onCameraIdle();
- quickZoom = false;
- break;
- }
-
- // notify camera change listener
- cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
-
- // Single finger double tap
- if (focalPoint != null) {
- // User provided focal point
- transform.zoom(true, focalPoint);
- } else {
- // Zoom in on gesture
- transform.zoom(true, new PointF(e.getX(), e.getY()));
- }
- if (isZoomValid(transform)) {
- MapEventFactory mapEventFactory = new MapEventFactory();
- LatLng latLng = projection.fromScreenLocation(new PointF(e.getX(), e.getY()));
- MapState doubleTap = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom());
- doubleTap.setGesture(Events.DOUBLE_TAP);
- Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, doubleTap));
- }
- break;
- }
-
+ public boolean onDown(MotionEvent motionEvent) {
return true;
}
@Override
public boolean onSingleTapUp(MotionEvent motionEvent) {
- // Cancel any animation
transform.cancelTransitions();
return true;
}
@@ -378,31 +319,51 @@ final class MapGestureDetector {
notifyOnMapClickListeners(tapPoint);
}
- if (isZoomValid(transform)) {
- MapEventFactory mapEventFactory = new MapEventFactory();
- LatLng latLng = projection.fromScreenLocation(new PointF(motionEvent.getX(), motionEvent.getY()));
- MapState singleTap = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom());
- singleTap.setGesture(Events.SINGLE_TAP);
- Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, singleTap));
- }
+ sendTelemetryEvent(Events.SINGLE_TAP, new PointF(motionEvent.getX(), motionEvent.getY()));
return true;
}
@Override
- public void onLongPress(MotionEvent motionEvent) {
- PointF longClickPoint = new PointF(motionEvent.getX(), motionEvent.getY());
+ public boolean onDoubleTapEvent(MotionEvent motionEvent) {
+ int action = motionEvent.getActionMasked();
+ if (action == MotionEvent.ACTION_DOWN) {
+ executeDoubleTap = true;
+ }
+ if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) {
+ if (!uiSettings.isZoomGesturesEnabled() || !uiSettings.isDoubleTapGesturesEnabled() || !executeDoubleTap) {
+ return false;
+ }
+
+ transform.cancelTransitions();
+ cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
- if (!quickZoom) {
- notifyOnMapLongClickListeners(longClickPoint);
+ // Single finger double tap
+ if (focalPoint != null) {
+ // User provided focal point
+ transform.zoomIn(focalPoint);
+ } else {
+ // Zoom in on gesture
+ transform.zoomIn(new PointF(motionEvent.getX(), motionEvent.getY()));
+ }
+
+ sendTelemetryEvent(Events.DOUBLE_TAP, new PointF(motionEvent.getX(), motionEvent.getY()));
+
+ return true;
}
+ return super.onDoubleTapEvent(motionEvent);
+ }
+
+ @Override
+ public void onLongPress(MotionEvent motionEvent) {
+ PointF longClickPoint = new PointF(motionEvent.getX(), motionEvent.getY());
+ notifyOnMapLongClickListeners(longClickPoint);
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- if ((!trackingSettings.isScrollGestureCurrentlyEnabled()) || recentScaleGestureOccurred) {
+ if ((!uiSettings.isScrollGesturesEnabled())) {
// don't allow a fling is scroll is disabled
- // and ignore when a scale gesture has occurred
return false;
}
@@ -415,11 +376,7 @@ final class MapGestureDetector {
return false;
}
- trackingSettings.resetTrackingModesIfRequired(true, false, false);
-
- // cancel any animation
transform.cancelTransitions();
-
cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
// tilt results in a bigger translation, limiting input for #5281
@@ -435,230 +392,149 @@ final class MapGestureDetector {
transform.moveBy(offsetX, offsetY, animationTime);
notifyOnFlingListeners();
+
return true;
}
+ }
- // Called for drags
+ private final class MoveGestureListener extends MoveGestureDetector.SimpleOnMoveGestureListener {
@Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
- if (!trackingSettings.isScrollGestureCurrentlyEnabled()) {
+ public boolean onMoveBegin(MoveGestureDetector detector) {
+ if (!uiSettings.isScrollGesturesEnabled()) {
return false;
}
- if (tiltGestureOccurred) {
- return false;
- }
+ transform.cancelTransitions();
+ cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
- if (!scrollGestureOccurred) {
- scrollGestureOccurred = true;
+ sendTelemetryEvent(Events.PAN, detector.getFocalPoint());
- // Cancel any animation
- if (!scaleGestureOccurred) {
- transform.cancelTransitions();
- cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
- }
+ notifyOnMoveBeginListeners(detector);
- if (isZoomValid(transform)) {
- MapEventFactory mapEventFactory = new MapEventFactory();
- LatLng latLng = projection.fromScreenLocation(new PointF(e1.getX(), e1.getY()));
- MapState pan = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom());
- pan.setGesture(Events.PAN);
- Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, pan));
- }
- }
+ return true;
+ }
- // reset tracking if needed
- trackingSettings.resetTrackingModesIfRequired(true, false, false);
+ @Override
+ public boolean onMove(MoveGestureDetector detector, float distanceX, float distanceY) {
+ // dispatching start even once more if another detector ended, and this one didn't
+ cameraChangeDispatcher.onCameraMoveStarted(CameraChangeDispatcher.REASON_API_GESTURE);
// Scroll the map
transform.moveBy(-distanceX, -distanceY, 0 /*no duration*/);
notifyOnScrollListeners();
+ notifyOnMoveListeners(detector);
return true;
}
- }
-
- void notifyOnMapClickListeners(PointF tapPoint) {
- // deprecated API
- if (onMapClickListener != null) {
- onMapClickListener.onMapClick(projection.fromScreenLocation(tapPoint));
- }
- // new API
- for (MapboxMap.OnMapClickListener listener : onMapClickListenerList) {
- listener.onMapClick(projection.fromScreenLocation(tapPoint));
+ @Override
+ public void onMoveEnd(MoveGestureDetector detector, float velocityX, float velocityY) {
+ cameraChangeDispatcher.onCameraIdle();
+ notifyOnMoveEndListeners(detector);
}
}
- void notifyOnMapLongClickListeners(PointF longClickPoint) {
- // deprecated API
- if (onMapLongClickListener != null) {
- onMapLongClickListener.onMapLongClick(projection.fromScreenLocation(longClickPoint));
- }
+ private final class ScaleGestureListener extends StandardScaleGestureDetector.SimpleStandardOnScaleGestureListener {
- // new API
- for (MapboxMap.OnMapLongClickListener listener : onMapLongClickListenerList) {
- listener.onMapLongClick(projection.fromScreenLocation(longClickPoint));
- }
- }
+ private final float minimumVelocity;
- void notifyOnFlingListeners() {
- // deprecated API
- if (onFlingListener != null) {
- onFlingListener.onFling();
- }
+ private PointF scaleFocalPoint;
+ private boolean quickZoom;
- // new API
- for (MapboxMap.OnFlingListener listener : onFlingListenerList) {
- listener.onFling();
+ ScaleGestureListener(float minimumVelocity) {
+ this.minimumVelocity = minimumVelocity;
}
- }
- void notifyOnScrollListeners() {
- //deprecated API
- if (onScrollListener != null) {
- onScrollListener.onScroll();
- }
-
- // new API
- for (MapboxMap.OnScrollListener listener : onScrollListenerList) {
- listener.onScroll();
- }
- }
-
- /**
- * Responsible for handling two finger gestures and double-tap drag gestures.
- */
- private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
-
- private static final int ANIMATION_TIME_MULTIPLIER = 77;
- private static final double ZOOM_DISTANCE_DIVIDER = 5;
-
- private float scaleFactor = 1.0f;
- private PointF scalePointBegin;
-
- // Called when two fingers first touch the screen
@Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
+ public boolean onScaleBegin(StandardScaleGestureDetector detector) {
if (!uiSettings.isZoomGesturesEnabled()) {
return false;
}
- recentScaleGestureOccurred = true;
- scalePointBegin = new PointF(detector.getFocusX(), detector.getFocusY());
- scaleBeginTime = detector.getEventTime();
- if (isZoomValid(transform)) {
- MapEventFactory mapEventFactory = new MapEventFactory();
- LatLng latLng = projection.fromScreenLocation(new PointF(detector.getFocusX(), detector.getFocusY()));
- MapState pinch = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom());
- pinch.setGesture(Events.PINCH);
- Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, pinch));
+ transform.cancelTransitions();
+ cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
+
+ quickZoom = detector.getPointersCount() == 1;
+ if (quickZoom) {
+ // when quickzoom, dismiss double tap and disable move gesture
+ executeDoubleTap = false;
+ gesturesManager.getMoveGestureDetector().setEnabled(false);
}
+
+ // increase rotate angle threshold when scale is detected first
+ gesturesManager.getRotateGestureDetector().setAngleThreshold(
+ gesturesManager.getRotateGestureDetector().getDefaultAngleThreshold()
+ + MapboxConstants.ROTATION_THRESHOLD_INCREASE_WHEN_SCALING
+ );
+
+ // setting focalPoint in #onScaleBegin() as well, because #onScale() might not get called before #onScaleEnd()
+ setScaleFocalPoint(detector);
+
+ sendTelemetryEvent(Events.PINCH, scaleFocalPoint);
+
+ notifyOnScaleBeginListeners(detector);
+
return true;
}
- // Called each time a finger moves
- // Called for pinch zooms and quickzooms/quickscales
@Override
- public boolean onScale(ScaleGestureDetector detector) {
- if (!uiSettings.isZoomGesturesEnabled()) {
- return super.onScale(detector);
- }
+ public boolean onScale(StandardScaleGestureDetector detector) {
+ // dispatching start even once more if another detector ended, and this one didn't
+ cameraChangeDispatcher.onCameraMoveStarted(CameraChangeDispatcher.REASON_API_GESTURE);
- wasZoomingIn = (Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2)) > 0;
- if (tiltGestureOccurred) {
- return false;
- }
-
- // Ignore short touches in case it is a tap
- // Also ignore small scales
- long time = detector.getEventTime();
- long interval = time - scaleBeginTime;
- if (!scaleGestureOccurred && (interval <= ViewConfiguration.getTapTimeout())) {
- return false;
- }
-
- // If scale is large enough ignore a tap
- scaleFactor *= detector.getScaleFactor();
- if ((scaleFactor > 1.1f) || (scaleFactor < 0.9f)) {
- // notify camera change listener
- cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
- scaleGestureOccurred = true;
- }
+ setScaleFocalPoint(detector);
- if (!scaleGestureOccurred) {
- return false;
- }
+ float scaleFactor = detector.getScaleFactor();
+ double zoomBy = getNewZoom(scaleFactor, quickZoom);
+ transform.zoomBy(zoomBy, scaleFocalPoint);
- // Gesture is a quickzoom if there aren't two fingers
- if (!quickZoom && !twoTap) {
- cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
- }
- quickZoom = !twoTap;
+ notifyOnScaleListeners(detector);
- // make an assumption here; if the zoom center is specified by the gesture, it's NOT going
- // to be in the center of the map. Therefore the zoom will translate the map center, so tracking
- // should be disabled.
- trackingSettings.resetTrackingModesIfRequired(!quickZoom, false, false);
- // Scale the map
- if (focalPoint != null) {
- // arround user provided focal point
- transform.zoomBy(Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2), focalPoint.x, focalPoint.y);
- } else if (quickZoom) {
- cameraChangeDispatcher.onCameraMove();
- // clamp scale factors we feed to core #7514
- float scaleFactor = detector.getScaleFactor();
- // around center map
- double zoomBy = Math.log(scaleFactor) / Math.log(Math.PI / 2);
- boolean negative = zoomBy < 0;
- zoomBy = MathUtils.clamp(Math.abs(zoomBy),
- MapboxConstants.MINIMUM_SCALE_FACTOR_CLAMP,
- MapboxConstants.MAXIMUM_SCALE_FACTOR_CLAMP);
- transform.zoomBy(negative ? -zoomBy : zoomBy, uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
- recentScaleGestureOccurred = true;
- } else {
- // around gesture
- transform.zoomBy(Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2),
- scalePointBegin.x, scalePointBegin.y);
- }
return true;
}
- // Called when fingers leave screen
@Override
- public void onScaleEnd(final ScaleGestureDetector detector) {
- if (velocityTracker == null) {
- return;
- }
+ public void onScaleEnd(StandardScaleGestureDetector detector, float velocityX, float velocityY) {
+ cameraChangeDispatcher.onCameraIdle();
- if (rotateGestureOccurred || quickZoom) {
- reset();
- return;
+ if (quickZoom) {
+ //if quickzoom, re-enabling move gesture detector
+ gesturesManager.getMoveGestureDetector().setEnabled(true);
}
- double velocityXY = Math.abs(velocityTracker.getYVelocity()) + Math.abs(velocityTracker.getXVelocity());
- if (velocityXY > MapboxConstants.VELOCITY_THRESHOLD_IGNORE_FLING / 2) {
- scaleAnimating = true;
- double zoomAddition = calculateScale(velocityXY);
+ // resetting default angle threshold
+ gesturesManager.getRotateGestureDetector().setAngleThreshold(
+ gesturesManager.getRotateGestureDetector().getDefaultAngleThreshold()
+ );
+
+ float velocityXY = Math.abs(velocityX) + Math.abs(velocityY);
+ if (velocityXY > minimumVelocity) {
+ double zoomAddition = calculateScale(velocityXY, detector.isScalingOut());
double currentZoom = transform.getRawZoom();
- long animationTime = (long) (Math.log(velocityXY) * ANIMATION_TIME_MULTIPLIER);
- createScaleAnimator(currentZoom, zoomAddition, animationTime).start();
- } else if (!scaleAnimating) {
- reset();
+ long animationTime = (long) (Math.abs(zoomAddition) * 1000 / 4);
+ scaleAnimator = createScaleAnimator(currentZoom, zoomAddition, animationTime);
+ scheduleAnimator(scaleAnimator);
}
+
+ notifyOnScaleEndListeners(detector);
}
- private void reset() {
- scaleAnimating = false;
- scaleGestureOccurred = false;
- scaleBeginTime = 0;
- scaleFactor = 1.0f;
- cameraChangeDispatcher.onCameraIdle();
+ private void setScaleFocalPoint(StandardScaleGestureDetector detector) {
+ if (focalPoint != null) {
+ // around user provided focal point
+ scaleFocalPoint = focalPoint;
+ } else if (quickZoom) {
+ // around center
+ scaleFocalPoint = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
+ } else {
+ // around gesture
+ scaleFocalPoint = detector.getFocalPoint();
+ }
}
- private double calculateScale(double velocityXY) {
- double zoomAddition = (float) (Math.log(velocityXY) / ZOOM_DISTANCE_DIVIDER);
- if (!wasZoomingIn) {
+ private double calculateScale(double velocityXY, boolean isScalingOut) {
+ double zoomAddition = (float) Math.log(velocityXY / 1000 + 1);
+ if (isScalingOut) {
zoomAddition = -zoomAddition;
}
return zoomAddition;
@@ -667,272 +543,387 @@ final class MapGestureDetector {
private Animator createScaleAnimator(double currentZoom, double zoomAddition, long animationTime) {
ValueAnimator animator = ValueAnimator.ofFloat((float) currentZoom, (float) (currentZoom + zoomAddition));
animator.setDuration(animationTime);
- animator.setInterpolator(new FastOutSlowInInterpolator());
+ animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
- transform.setZoom((Float) animation.getAnimatedValue(), scalePointBegin, 0, true);
+ transform.setZoom((Float) animation.getAnimatedValue(), scaleFocalPoint, 0);
}
});
+
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
+ transform.cancelTransitions();
cameraChangeDispatcher.onCameraMoveStarted(REASON_API_ANIMATION);
}
@Override
public void onAnimationCancel(Animator animation) {
- reset();
+ transform.cancelTransitions();
}
@Override
public void onAnimationEnd(Animator animation) {
- reset();
+ cameraChangeDispatcher.onCameraIdle();
}
});
return animator;
}
- }
- /**
- * Responsible for handling rotation gestures.
- */
- private class RotateGestureListener extends RotateGestureDetector.SimpleOnRotateGestureListener {
+ private double getNewZoom(float scaleFactor, boolean quickZoom) {
+ double zoomBy = Math.log(scaleFactor) / Math.log(Math.PI / 2);
+ if (quickZoom) {
+ // clamp scale factors we feed to core #7514
+ boolean negative = zoomBy < 0;
+ zoomBy = MathUtils.clamp(Math.abs(zoomBy),
+ MapboxConstants.MINIMUM_SCALE_FACTOR_CLAMP,
+ MapboxConstants.MAXIMUM_SCALE_FACTOR_CLAMP);
+ return negative ? -zoomBy : zoomBy;
+ }
+ return zoomBy;
+ }
+ }
- private static final float ROTATE_INVOKE_ANGLE = 15.30f;
- private static final float ROTATE_LIMITATION_ANGLE = 3.35f;
- private static final float ROTATE_LIMITATION_DURATION = ROTATE_LIMITATION_ANGLE * 1.85f;
+ private final class RotateGestureListener extends RotateGestureDetector.SimpleOnRotateGestureListener {
+ private PointF rotateFocalPoint;
+ private final float minimumScaleSpanWhenRotating;
+ private final float minimumAngularVelocity;
- private long beginTime = 0;
- private boolean started = false;
+ RotateGestureListener(float minimumScaleSpanWhenRotating, float minimumAngularVelocity) {
+ this.minimumScaleSpanWhenRotating = minimumScaleSpanWhenRotating;
+ this.minimumAngularVelocity = minimumAngularVelocity;
+ }
- // Called when two fingers first touch the screen
@Override
public boolean onRotateBegin(RotateGestureDetector detector) {
- if (!trackingSettings.isRotateGestureCurrentlyEnabled()) {
+ if (!uiSettings.isRotateGesturesEnabled()) {
return false;
}
- // notify camera change listener
+ transform.cancelTransitions();
cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
- beginTime = detector.getEventTime();
- return true;
- }
+ // when rotation starts, interrupting scale and increasing the threshold
+ // to make rotation without scaling easier
+ gesturesManager.getStandardScaleGestureDetector().setSpanSinceStartThreshold(minimumScaleSpanWhenRotating);
+ gesturesManager.getStandardScaleGestureDetector().interrupt();
- // Called each time one of the two fingers moves
- // Called for rotation
- @Override
- public boolean onRotate(RotateGestureDetector detector) {
- if (!trackingSettings.isRotateGestureCurrentlyEnabled() || tiltGestureOccurred) {
- return false;
- }
+ // setting in #onRotateBegin() as well, because #onRotate() might not get called before #onRotateEnd()
+ setRotateFocalPoint(detector);
- // If rotate is large enough ignore a tap
- // Also is zoom already started, don't rotate
- float angle = detector.getRotationDegreesDelta();
- if (Math.abs(angle) >= ROTATE_INVOKE_ANGLE) {
- if (isZoomValid(transform)) {
- MapEventFactory mapEventFactory = new MapEventFactory();
- LatLng latLng = projection.fromScreenLocation(new PointF(detector.getFocusX(), detector.getFocusY()));
- MapState rotation = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom());
- rotation.setGesture(Events.ROTATION);
- Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, rotation));
- }
- started = true;
- }
+ sendTelemetryEvent(Events.ROTATION, rotateFocalPoint);
- if (!started) {
- return false;
- }
+ notifyOnRotateBeginListeners(detector);
- wasClockwiseRotating = detector.getRotationDegreesDelta() > 0;
- if (scaleBeginTime != 0) {
- rotateGestureOccurred = true;
- }
+ return true;
+ }
- // rotation constitutes translation of anything except the center of
- // rotation, so cancel both location and bearing tracking if required
- trackingSettings.resetTrackingModesIfRequired(true, true, false);
+ @Override
+ public boolean onRotate(RotateGestureDetector detector, float rotationDegreesSinceLast,
+ float rotationDegreesSinceFirst) {
+ // dispatching start even once more if another detector ended, and this one didn't
+ cameraChangeDispatcher.onCameraMoveStarted(CameraChangeDispatcher.REASON_API_GESTURE);
+
+ setRotateFocalPoint(detector);
// Calculate map bearing value
- double bearing = transform.getRawBearing() + angle;
+ double bearing = transform.getRawBearing() + rotationDegreesSinceLast;
// Rotate the map
- if (focalPoint != null) {
- // User provided focal point
- transform.setBearing(bearing, focalPoint.x, focalPoint.y);
- } else {
- // around gesture
- transform.setBearing(bearing, detector.getFocusX(), detector.getFocusY());
- }
+ transform.setBearing(bearing, rotateFocalPoint.x, rotateFocalPoint.y);
+
+ notifyOnRotateListeners(detector);
+
return true;
}
- // Called when the fingers leave the screen
@Override
- public void onRotateEnd(RotateGestureDetector detector) {
- long interval = detector.getEventTime() - beginTime;
- if ((!started && (interval <= ViewConfiguration.getTapTimeout())) || scaleAnimating || interval > 500) {
- reset();
+ public void onRotateEnd(RotateGestureDetector detector, float velocityX, float velocityY, float angularVelocity) {
+ cameraChangeDispatcher.onCameraIdle();
+
+ // resetting default scale threshold values
+ gesturesManager.getStandardScaleGestureDetector().setSpanSinceStartThreshold(
+ gesturesManager.getStandardScaleGestureDetector().getDefaultSpanSinceStartThreshold());
+
+ if (Math.abs(angularVelocity) < minimumAngularVelocity) {
return;
}
- double angularVelocity = calculateVelocityVector(detector);
- if (Math.abs(angularVelocity) > 0.001 && rotateGestureOccurred && !rotateAnimating) {
- animateRotateVelocity();
- } else if (!rotateAnimating) {
- reset();
- }
- }
+ boolean negative = angularVelocity < 0;
+ angularVelocity = (float) Math.pow(angularVelocity, 2);
+ angularVelocity = MathUtils.clamp(
+ angularVelocity, MapboxConstants.MINIMUM_ANGULAR_VELOCITY, MapboxConstants.MAXIMUM_ANGULAR_VELOCITY);
- private void reset() {
- beginTime = 0;
- started = false;
- rotateAnimating = false;
- rotateGestureOccurred = false;
+ long animationTime = (long) (Math.log(angularVelocity + 1) * 500);
- if (!twoTap) {
- cameraChangeDispatcher.onCameraIdle();
+ if (negative) {
+ angularVelocity = -angularVelocity;
}
- }
- private void animateRotateVelocity() {
- rotateAnimating = true;
- double currentRotation = transform.getRawBearing();
- double rotateAdditionDegrees = calculateVelocityInDegrees();
- createAnimator(currentRotation, rotateAdditionDegrees).start();
- }
+ rotateAnimator = createRotateAnimator(angularVelocity, animationTime);
+ scheduleAnimator(rotateAnimator);
- private double calculateVelocityVector(RotateGestureDetector detector) {
- return ((detector.getFocusX() * velocityTracker.getYVelocity())
- + (detector.getFocusY() * velocityTracker.getXVelocity()))
- / (Math.pow(detector.getFocusX(), 2) + Math.pow(detector.getFocusY(), 2));
+ notifyOnRotateEndListeners(detector);
}
- private double calculateVelocityInDegrees() {
- double angleRadians = Math.atan2(velocityTracker.getXVelocity(), velocityTracker.getYVelocity());
- double angle = angleRadians / (Math.PI / 180);
- if (angle <= 0) {
- angle += 360;
- }
-
- // limit the angle
- angle = angle / ROTATE_LIMITATION_ANGLE;
-
- // correct direction
- if (!wasClockwiseRotating) {
- angle = -angle;
+ private void setRotateFocalPoint(RotateGestureDetector detector) {
+ if (focalPoint != null) {
+ // User provided focal point
+ rotateFocalPoint = focalPoint;
+ } else {
+ // around gesture
+ rotateFocalPoint = detector.getFocalPoint();
}
-
- return angle;
}
- private Animator createAnimator(double currentRotation, double rotateAdditionDegrees) {
- ValueAnimator animator = ValueAnimator.ofFloat(
- (float) currentRotation,
- (float) (currentRotation + rotateAdditionDegrees)
- );
- animator.setDuration((long) (Math.abs(rotateAdditionDegrees) * ROTATE_LIMITATION_DURATION));
+ private Animator createRotateAnimator(float angularVelocity, long animationTime) {
+ ValueAnimator animator = ValueAnimator.ofFloat(angularVelocity, 0f);
+ animator.setDuration(animationTime);
+ animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
- transform.setBearing((Float) animation.getAnimatedValue());
+ transform.setBearing(
+ transform.getRawBearing() + (float) animation.getAnimatedValue(),
+ rotateFocalPoint.x, rotateFocalPoint.y,
+ 0L
+ );
}
});
+
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
+ transform.cancelTransitions();
cameraChangeDispatcher.onCameraMoveStarted(REASON_API_ANIMATION);
}
@Override
public void onAnimationCancel(Animator animation) {
- reset();
+ cameraChangeDispatcher.onCameraIdle();
}
@Override
public void onAnimationEnd(Animator animation) {
- reset();
+ cameraChangeDispatcher.onCameraIdle();
}
});
+
return animator;
}
}
- /**
- * Responsible for handling 2 finger shove gestures.
- */
- private class ShoveGestureListener implements ShoveGestureDetector.OnShoveGestureListener {
-
- private long beginTime = 0;
- private float totalDelta = 0.0f;
-
+ private final class ShoveGestureListener extends ShoveGestureDetector.SimpleOnShoveGestureListener {
@Override
public boolean onShoveBegin(ShoveGestureDetector detector) {
if (!uiSettings.isTiltGesturesEnabled()) {
return false;
}
- // notify camera change listener
+ transform.cancelTransitions();
cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
+
+ sendTelemetryEvent(Events.PITCH, detector.getFocalPoint());
+
+ // disabling move gesture during shove
+ gesturesManager.getMoveGestureDetector().setEnabled(false);
+
+ notifyOnShoveBeginListeners(detector);
+
return true;
}
@Override
- public void onShoveEnd(ShoveGestureDetector detector) {
- beginTime = 0;
- totalDelta = 0.0f;
- tiltGestureOccurred = false;
+ public boolean onShove(ShoveGestureDetector detector, float deltaPixelsSinceLast, float deltaPixelsSinceStart) {
+ // dispatching start even once more if another detector ended, and this one didn't
+ cameraChangeDispatcher.onCameraMoveStarted(CameraChangeDispatcher.REASON_API_GESTURE);
+
+ // Get tilt value (scale and clamp)
+ double pitch = transform.getTilt();
+ pitch -= MapboxConstants.SHOVE_PIXEL_CHANGE_FACTOR * deltaPixelsSinceLast;
+ pitch = MathUtils.clamp(pitch, MapboxConstants.MINIMUM_TILT, MapboxConstants.MAXIMUM_TILT);
+
+ // Tilt the map
+ transform.setTilt(pitch);
+
+ notifyOnShoveListeners(detector);
+
+ return true;
}
@Override
- public boolean onShove(ShoveGestureDetector detector) {
- if (!uiSettings.isTiltGesturesEnabled()) {
- return false;
- }
+ public void onShoveEnd(ShoveGestureDetector detector, float velocityX, float velocityY) {
+ cameraChangeDispatcher.onCameraIdle();
- // Ignore short touches in case it is a tap
- // Also ignore small tilt
- long time = detector.getEventTime();
- long interval = time - beginTime;
- if (!tiltGestureOccurred && (interval <= ViewConfiguration.getTapTimeout())) {
- return false;
- }
+ // re-enabling move gesture
+ gesturesManager.getMoveGestureDetector().setEnabled(true);
- // If tilt is large enough ignore a tap
- // Also if zoom already started, don't tilt
- totalDelta += detector.getShovePixelsDelta();
- if (!tiltGestureOccurred && ((totalDelta > 10.0f) || (totalDelta < -10.0f))) {
- tiltGestureOccurred = true;
- beginTime = detector.getEventTime();
- if (isZoomValid(transform)) {
- MapEventFactory mapEventFactory = new MapEventFactory();
- LatLng latLng = projection.fromScreenLocation(new PointF(detector.getFocusX(), detector.getFocusY()));
- MapState pitch = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom());
- pitch.setGesture(Events.PITCH);
- Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, pitch));
- }
- }
+ notifyOnShoveEndListeners(detector);
+ }
+ }
- if (!tiltGestureOccurred) {
+ private final class TapGestureListener implements MultiFingerTapGestureDetector.OnMultiFingerTapGestureListener {
+ @Override
+ public boolean onMultiFingerTap(MultiFingerTapGestureDetector detector, int pointersCount) {
+ if (!uiSettings.isZoomGesturesEnabled() || pointersCount != 2) {
return false;
}
- // Get tilt value (scale and clamp)
- double pitch = transform.getTilt();
- pitch -= 0.1 * detector.getShovePixelsDelta();
- pitch = Math.max(MapboxConstants.MINIMUM_TILT, Math.min(MapboxConstants.MAXIMUM_TILT, pitch));
+ transform.cancelTransitions();
+ cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
+
+ if (focalPoint != null) {
+ transform.zoomOut(focalPoint);
+ } else {
+ transform.zoomOut(detector.getFocalPoint());
+ }
- // Tilt the map
- transform.setTilt(pitch);
return true;
}
}
+ private void sendTelemetryEvent(String eventType, PointF focalPoint) {
+ if (isZoomValid(transform)) {
+ MapEventFactory mapEventFactory = new MapEventFactory();
+ LatLng latLng = projection.fromScreenLocation(focalPoint);
+ MapState state = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom());
+ state.setGesture(eventType);
+ Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, state));
+ }
+ }
+
+ private boolean isZoomValid(Transform transform) {
+ if (transform == null) {
+ return false;
+ }
+ double mapZoom = transform.getZoom();
+ return mapZoom >= MapboxConstants.MINIMUM_ZOOM && mapZoom <= MapboxConstants.MAXIMUM_ZOOM;
+ }
+
+ void notifyOnMapClickListeners(PointF tapPoint) {
+ // deprecated API
+ if (onMapClickListener != null) {
+ onMapClickListener.onMapClick(projection.fromScreenLocation(tapPoint));
+ }
+
+ // new API
+ for (MapboxMap.OnMapClickListener listener : onMapClickListenerList) {
+ listener.onMapClick(projection.fromScreenLocation(tapPoint));
+ }
+ }
+
+ void notifyOnMapLongClickListeners(PointF longClickPoint) {
+ // deprecated API
+ if (onMapLongClickListener != null) {
+ onMapLongClickListener.onMapLongClick(projection.fromScreenLocation(longClickPoint));
+ }
+
+ // new API
+ for (MapboxMap.OnMapLongClickListener listener : onMapLongClickListenerList) {
+ listener.onMapLongClick(projection.fromScreenLocation(longClickPoint));
+ }
+ }
+
+ void notifyOnFlingListeners() {
+ // deprecated API
+ if (onFlingListener != null) {
+ onFlingListener.onFling();
+ }
+
+ // new API
+ for (MapboxMap.OnFlingListener listener : onFlingListenerList) {
+ listener.onFling();
+ }
+ }
+
+ void notifyOnScrollListeners() {
+ //deprecated API
+ if (onScrollListener != null) {
+ onScrollListener.onScroll();
+ }
+
+ // new API
+ for (MapboxMap.OnScrollListener listener : onScrollListenerList) {
+ listener.onScroll();
+ }
+ }
+
+ void notifyOnMoveBeginListeners(MoveGestureDetector detector) {
+ for (MapboxMap.OnMoveListener listener : onMoveListenerList) {
+ listener.onMoveBegin(detector);
+ }
+ }
+
+ void notifyOnMoveListeners(MoveGestureDetector detector) {
+ for (MapboxMap.OnMoveListener listener : onMoveListenerList) {
+ listener.onMove(detector);
+ }
+ }
+
+ void notifyOnMoveEndListeners(MoveGestureDetector detector) {
+ for (MapboxMap.OnMoveListener listener : onMoveListenerList) {
+ listener.onMoveEnd(detector);
+ }
+ }
+
+ void notifyOnRotateBeginListeners(RotateGestureDetector detector) {
+ for (MapboxMap.OnRotateListener listener : onRotateListenerList) {
+ listener.onRotateBegin(detector);
+ }
+ }
+
+ void notifyOnRotateListeners(RotateGestureDetector detector) {
+ for (MapboxMap.OnRotateListener listener : onRotateListenerList) {
+ listener.onRotate(detector);
+ }
+ }
+
+ void notifyOnRotateEndListeners(RotateGestureDetector detector) {
+ for (MapboxMap.OnRotateListener listener : onRotateListenerList) {
+ listener.onRotateEnd(detector);
+ }
+ }
+
+ void notifyOnScaleBeginListeners(StandardScaleGestureDetector detector) {
+ for (MapboxMap.OnScaleListener listener : onScaleListenerList) {
+ listener.onScaleBegin(detector);
+ }
+ }
+
+ void notifyOnScaleListeners(StandardScaleGestureDetector detector) {
+ for (MapboxMap.OnScaleListener listener : onScaleListenerList) {
+ listener.onScale(detector);
+ }
+ }
+
+ void notifyOnScaleEndListeners(StandardScaleGestureDetector detector) {
+ for (MapboxMap.OnScaleListener listener : onScaleListenerList) {
+ listener.onScaleEnd(detector);
+ }
+ }
+
+ void notifyOnShoveBeginListeners(ShoveGestureDetector detector) {
+ for (MapboxMap.OnShoveListener listener : onShoveListenerList) {
+ listener.onShoveBegin(detector);
+ }
+ }
+
+ void notifyOnShoveListeners(ShoveGestureDetector detector) {
+ for (MapboxMap.OnShoveListener listener : onShoveListenerList) {
+ listener.onShove(detector);
+ }
+ }
+
+ void notifyOnShoveEndListeners(ShoveGestureDetector detector) {
+ for (MapboxMap.OnShoveListener listener : onShoveListenerList) {
+ listener.onShoveEnd(detector);
+ }
+ }
+
void setOnMapClickListener(MapboxMap.OnMapClickListener onMapClickListener) {
this.onMapClickListener = onMapClickListener;
}
@@ -981,14 +972,43 @@ final class MapGestureDetector {
onScrollListenerList.remove(onScrollListener);
}
- private boolean isZoomValid(Transform transform) {
- if (transform == null) {
- return false;
- }
- double mapZoom = transform.getZoom();
- if (mapZoom < MapboxConstants.MINIMUM_ZOOM || mapZoom > MapboxConstants.MAXIMUM_ZOOM) {
- return false;
- }
- return true;
+ void addOnMoveListener(MapboxMap.OnMoveListener listener) {
+ onMoveListenerList.add(listener);
+ }
+
+ void removeOnMoveListener(MapboxMap.OnMoveListener listener) {
+ onMoveListenerList.remove(listener);
+ }
+
+ void addOnRotateListener(MapboxMap.OnRotateListener listener) {
+ onRotateListenerList.add(listener);
+ }
+
+ void removeOnRotateListener(MapboxMap.OnRotateListener listener) {
+ onRotateListenerList.remove(listener);
+ }
+
+ void addOnScaleListener(MapboxMap.OnScaleListener listener) {
+ onScaleListenerList.add(listener);
+ }
+
+ void removeOnScaleListener(MapboxMap.OnScaleListener listener) {
+ onScaleListenerList.remove(listener);
+ }
+
+ void addShoveListener(MapboxMap.OnShoveListener listener) {
+ onShoveListenerList.add(listener);
+ }
+
+ void removeShoveListener(MapboxMap.OnShoveListener listener) {
+ onShoveListenerList.remove(listener);
+ }
+
+ AndroidGesturesManager getGesturesManager() {
+ return gesturesManager;
+ }
+
+ void setGesturesManager(AndroidGesturesManager gesturesManager) {
+ this.gesturesManager = gesturesManager;
}
-}
+} \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapKeyListener.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapKeyListener.java
index d1f01a30f7..9bd9499fff 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapKeyListener.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapKeyListener.java
@@ -128,7 +128,7 @@ final class MapKeyListener {
// Zoom out
PointF focalPoint = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
- transform.zoom(false, focalPoint);
+ transform.zoomOut(focalPoint);
return true;
default:
@@ -164,7 +164,7 @@ final class MapKeyListener {
// Zoom in
PointF focalPoint = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
- transform.zoom(true, focalPoint);
+ transform.zoomIn(focalPoint);
return true;
}
@@ -219,7 +219,7 @@ final class MapKeyListener {
if (currentTrackballLongPressTimeOut != null) {
// Zoom in
PointF focalPoint = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
- transform.zoom(true, focalPoint);
+ transform.zoomIn(focalPoint);
}
return true;
@@ -261,7 +261,7 @@ final class MapKeyListener {
if (!cancelled) {
// Zoom out
PointF pointF = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
- transform.zoom(false, pointF);
+ transform.zoomOut(pointF);
// Ensure the up action is not run
currentTrackballLongPressTimeOut = null;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
index cf70300c6e..9ace60b481 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
@@ -23,6 +23,7 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ZoomButtonsController;
+import com.mapbox.android.gestures.AndroidGesturesManager;
import com.mapbox.android.telemetry.AppUserTurnstile;
import com.mapbox.android.telemetry.Event;
import com.mapbox.android.telemetry.MapEventFactory;
@@ -42,8 +43,6 @@ import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings;
import com.mapbox.mapboxsdk.net.ConnectivityReceiver;
import com.mapbox.mapboxsdk.storage.FileSource;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.opengles.GL10;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -52,6 +51,9 @@ import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
import timber.log.Timber;
import static com.mapbox.mapboxsdk.maps.widgets.CompassView.TIME_MAP_NORTH_ANIMATION;
@@ -149,7 +151,7 @@ public class MapView extends FrameLayout {
focalPointInvalidator.addListener(createFocalPointChangeListener());
// callback for registering touch listeners
- RegisterTouchListener registerTouchListener = new RegisterTouchListener();
+ GesturesManagerInteractionListener registerTouchListener = new GesturesManagerInteractionListener();
// callback for zooming in the camera
CameraZoomInvalidator zoomInvalidator = new CameraZoomInvalidator();
@@ -184,7 +186,7 @@ public class MapView extends FrameLayout {
mapCallback.attachMapboxMap(mapboxMap);
// user input
- mapGestureDetector = new MapGestureDetector(context, transform, proj, uiSettings, trackingSettings,
+ mapGestureDetector = new MapGestureDetector(context, transform, proj, uiSettings,
annotationManager, cameraChangeDispatcher);
mapKeyListener = new MapKeyListener(transform, trackingSettings, uiSettings);
@@ -390,6 +392,7 @@ public class MapView extends FrameLayout {
public void onStop() {
if (mapboxMap != null) {
// map was destroyed before it was started
+ mapGestureDetector.cancelAnimators();
mapboxMap.onStop();
}
@@ -923,7 +926,7 @@ public class MapView extends FrameLayout {
}
}
- private class RegisterTouchListener implements MapboxMap.OnRegisterTouchListener {
+ private class GesturesManagerInteractionListener implements MapboxMap.OnGesturesManagerInteractionListener {
@Override
public void onSetMapClickListener(MapboxMap.OnMapClickListener listener) {
@@ -984,6 +987,56 @@ public class MapView extends FrameLayout {
public void onRemoveFlingListener(MapboxMap.OnFlingListener listener) {
mapGestureDetector.removeOnFlingListener(listener);
}
+
+ @Override
+ public void onAddMoveListener(MapboxMap.OnMoveListener listener) {
+ mapGestureDetector.addOnMoveListener(listener);
+ }
+
+ @Override
+ public void onRemoveMoveListener(MapboxMap.OnMoveListener listener) {
+ mapGestureDetector.removeOnMoveListener(listener);
+ }
+
+ @Override
+ public void onAddRotateListener(MapboxMap.OnRotateListener listener) {
+ mapGestureDetector.addOnRotateListener(listener);
+ }
+
+ @Override
+ public void onRemoveRotateListener(MapboxMap.OnRotateListener listener) {
+ mapGestureDetector.removeOnRotateListener(listener);
+ }
+
+ @Override
+ public void onAddScaleListener(MapboxMap.OnScaleListener listener) {
+ mapGestureDetector.addOnScaleListener(listener);
+ }
+
+ @Override
+ public void onRemoveScaleListener(MapboxMap.OnScaleListener listener) {
+ mapGestureDetector.removeOnScaleListener(listener);
+ }
+
+ @Override
+ public void onAddShoveListener(MapboxMap.OnShoveListener listener) {
+ mapGestureDetector.addShoveListener(listener);
+ }
+
+ @Override
+ public void onRemoveShoveListener(MapboxMap.OnShoveListener listener) {
+ mapGestureDetector.removeShoveListener(listener);
+ }
+
+ @Override
+ public AndroidGesturesManager getGesturesManager() {
+ return mapGestureDetector.getGesturesManager();
+ }
+
+ @Override
+ public void setGesturesManager(AndroidGesturesManager gesturesManager) {
+ mapGestureDetector.setGesturesManager(gesturesManager);
+ }
}
private static class MapZoomControllerListener implements ZoomButtonsController.OnZoomListener {
@@ -1021,11 +1074,13 @@ public class MapView extends FrameLayout {
}
private void onZoom(boolean zoomIn, @Nullable PointF focalPoint) {
- if (focalPoint != null) {
- transform.zoom(zoomIn, focalPoint);
+ if (focalPoint == null) {
+ focalPoint = new PointF(mapWidth / 2, mapHeight / 2);
+ }
+ if (zoomIn) {
+ transform.zoomIn(focalPoint);
} else {
- PointF centerPoint = new PointF(mapWidth / 2, mapHeight / 2);
- transform.zoom(zoomIn, centerPoint);
+ transform.zoomOut(focalPoint);
}
}
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java
index 5573e2d243..69fc4d2f2d 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java
@@ -17,6 +17,11 @@ import android.view.View;
import android.view.ViewGroup;
import com.mapbox.android.core.location.LocationEngine;
+import com.mapbox.android.gestures.AndroidGesturesManager;
+import com.mapbox.android.gestures.MoveGestureDetector;
+import com.mapbox.android.gestures.RotateGestureDetector;
+import com.mapbox.android.gestures.ShoveGestureDetector;
+import com.mapbox.android.gestures.StandardScaleGestureDetector;
import com.mapbox.geojson.Feature;
import com.mapbox.geojson.Geometry;
import com.mapbox.mapboxsdk.annotations.Annotation;
@@ -73,13 +78,13 @@ public final class MapboxMap {
private final MyLocationViewSettings myLocationViewSettings;
private final CameraChangeDispatcher cameraChangeDispatcher;
- private final OnRegisterTouchListener onRegisterTouchListener;
+ private final OnGesturesManagerInteractionListener onGesturesManagerInteractionListener;
private MapboxMap.OnFpsChangedListener onFpsChangedListener;
private PointF focalPoint;
MapboxMap(NativeMapView map, Transform transform, UiSettings ui, TrackingSettings tracking,
- MyLocationViewSettings myLocationView, Projection projection, OnRegisterTouchListener listener,
+ MyLocationViewSettings myLocationView, Projection projection, OnGesturesManagerInteractionListener listener,
AnnotationManager annotations, CameraChangeDispatcher cameraChangeDispatcher) {
this.nativeMapView = map;
this.uiSettings = ui;
@@ -88,7 +93,7 @@ public final class MapboxMap {
this.myLocationViewSettings = myLocationView;
this.annotationManager = annotations.bind(this);
this.transform = transform;
- this.onRegisterTouchListener = listener;
+ this.onGesturesManagerInteractionListener = listener;
this.cameraChangeDispatcher = cameraChangeDispatcher;
}
@@ -1845,7 +1850,7 @@ public final class MapboxMap {
*/
@Deprecated
public void setOnScrollListener(@Nullable OnScrollListener listener) {
- onRegisterTouchListener.onSetScrollListener(listener);
+ onGesturesManagerInteractionListener.onSetScrollListener(listener);
}
/**
@@ -1855,7 +1860,7 @@ public final class MapboxMap {
* To unset the callback, use null.
*/
public void addOnScrollListener(@Nullable OnScrollListener listener) {
- onRegisterTouchListener.onAddScrollListener(listener);
+ onGesturesManagerInteractionListener.onAddScrollListener(listener);
}
/**
@@ -1865,7 +1870,7 @@ public final class MapboxMap {
* To unset the callback, use null.
*/
public void removeOnScrollListener(@Nullable OnScrollListener listener) {
- onRegisterTouchListener.onRemoveScrollListener(listener);
+ onGesturesManagerInteractionListener.onRemoveScrollListener(listener);
}
/**
@@ -1877,7 +1882,7 @@ public final class MapboxMap {
*/
@Deprecated
public void setOnFlingListener(@Nullable OnFlingListener listener) {
- onRegisterTouchListener.onSetFlingListener(listener);
+ onGesturesManagerInteractionListener.onSetFlingListener(listener);
}
/**
@@ -1887,7 +1892,7 @@ public final class MapboxMap {
* To unset the callback, use null.
*/
public void addOnFlingListener(@Nullable OnFlingListener listener) {
- onRegisterTouchListener.onAddFlingListener(listener);
+ onGesturesManagerInteractionListener.onAddFlingListener(listener);
}
/**
@@ -1897,7 +1902,98 @@ public final class MapboxMap {
* To unset the callback, use null.
*/
public void removeOnFlingListener(@Nullable OnFlingListener listener) {
- onRegisterTouchListener.onRemoveFlingListener(listener);
+ onGesturesManagerInteractionListener.onRemoveFlingListener(listener);
+ }
+
+ /**
+ * Adds a callback that's invoked when the map is moved.
+ *
+ * @param listener The callback that's invoked when the map is moved.
+ */
+ public void addOnMoveListener(OnMoveListener listener) {
+ onGesturesManagerInteractionListener.onAddMoveListener(listener);
+ }
+
+ /**
+ * Removes a callback that's invoked when the map is moved.
+ *
+ * @param listener The callback that's invoked when the map is moved.
+ */
+ public void removeOnMoveListener(OnMoveListener listener) {
+ onGesturesManagerInteractionListener.onRemoveMoveListener(listener);
+ }
+
+ /**
+ * Adds a callback that's invoked when the map is rotated.
+ *
+ * @param listener The callback that's invoked when the map is rotated.
+ */
+ public void addOnRotateListener(OnRotateListener listener) {
+ onGesturesManagerInteractionListener.onAddRotateListener(listener);
+ }
+
+ /**
+ * Removes a callback that's invoked when the map is rotated.
+ *
+ * @param listener The callback that's invoked when the map is rotated.
+ */
+ public void removeOnRotateListener(OnRotateListener listener) {
+ onGesturesManagerInteractionListener.onRemoveRotateListener(listener);
+ }
+
+ /**
+ * Adds a callback that's invoked when the map is scaled.
+ *
+ * @param listener The callback that's invoked when the map is scaled.
+ */
+ public void addOnScaleListener(OnScaleListener listener) {
+ onGesturesManagerInteractionListener.onAddScaleListener(listener);
+ }
+
+ /**
+ * Removes a callback that's invoked when the map is scaled.
+ *
+ * @param listener The callback that's invoked when the map is scaled.
+ */
+ public void removeOnScaleListener(OnScaleListener listener) {
+ onGesturesManagerInteractionListener.onRemoveScaleListener(listener);
+ }
+
+ /**
+ * Adds a callback that's invoked when the map is tilted.
+ *
+ * @param listener The callback that's invoked when the map is tilted.
+ */
+ public void addOnShoveListener(OnShoveListener listener) {
+ onGesturesManagerInteractionListener.onAddShoveListener(listener);
+ }
+
+ /**
+ * Remove a callback that's invoked when the map is tilted.
+ *
+ * @param listener The callback that's invoked when the map is tilted.
+ */
+ public void removeOnShoveListener(OnShoveListener listener) {
+ onGesturesManagerInteractionListener.onRemoveShoveListener(listener);
+ }
+
+ /**
+ * Sets a custom {@link AndroidGesturesManager} to handle {@link android.view.MotionEvent}s registered by the map.
+ *
+ * @param androidGesturesManager Gestures manager that interprets gestures based on the motion events.
+ * @see <a href="https://github.com/mapbox/mapbox-gestures-android">mapbox-gestures-android library</a>
+ */
+ public void setGesturesManager(AndroidGesturesManager androidGesturesManager) {
+ onGesturesManagerInteractionListener.setGesturesManager(androidGesturesManager);
+ }
+
+ /**
+ * Get current {@link AndroidGesturesManager} that handles {@link android.view.MotionEvent}s registered by the map.
+ *
+ * @return Current gestures manager.
+ */
+ public AndroidGesturesManager getGesturesManager() {
+ return onGesturesManagerInteractionListener.getGesturesManager();
}
/**
@@ -1909,7 +2005,7 @@ public final class MapboxMap {
*/
@Deprecated
public void setOnMapClickListener(@Nullable OnMapClickListener listener) {
- onRegisterTouchListener.onSetMapClickListener(listener);
+ onGesturesManagerInteractionListener.onSetMapClickListener(listener);
}
/**
@@ -1919,7 +2015,7 @@ public final class MapboxMap {
* To unset the callback, use null.
*/
public void addOnMapClickListener(@Nullable OnMapClickListener listener) {
- onRegisterTouchListener.onAddMapClickListener(listener);
+ onGesturesManagerInteractionListener.onAddMapClickListener(listener);
}
/**
@@ -1929,7 +2025,7 @@ public final class MapboxMap {
* To unset the callback, use null.
*/
public void removeOnMapClickListener(@Nullable OnMapClickListener listener) {
- onRegisterTouchListener.onRemoveMapClickListener(listener);
+ onGesturesManagerInteractionListener.onRemoveMapClickListener(listener);
}
/**
@@ -1941,7 +2037,7 @@ public final class MapboxMap {
*/
@Deprecated
public void setOnMapLongClickListener(@Nullable OnMapLongClickListener listener) {
- onRegisterTouchListener.onSetMapLongClickListener(listener);
+ onGesturesManagerInteractionListener.onSetMapLongClickListener(listener);
}
/**
@@ -1951,7 +2047,7 @@ public final class MapboxMap {
* To unset the callback, use null.
*/
public void addOnMapLongClickListener(@Nullable OnMapLongClickListener listener) {
- onRegisterTouchListener.onAddMapLongClickListener(listener);
+ onGesturesManagerInteractionListener.onAddMapLongClickListener(listener);
}
/**
@@ -1961,7 +2057,7 @@ public final class MapboxMap {
* To unset the callback, use null.
*/
public void removeOnMapLongClickListener(@Nullable OnMapLongClickListener listener) {
- onRegisterTouchListener.onRemoveMapLongClickListener(listener);
+ onGesturesManagerInteractionListener.onRemoveMapLongClickListener(listener);
}
/**
@@ -2220,7 +2316,9 @@ public final class MapboxMap {
* Interface definition for a callback to be invoked when the map is scrolled.
*
* @see MapboxMap#setOnScrollListener(OnScrollListener)
+ * @deprecated Use {@link OnMoveListener} instead.
*/
+ @Deprecated
public interface OnScrollListener {
/**
* Called when the map is scrolled.
@@ -2229,6 +2327,58 @@ public final class MapboxMap {
}
/**
+ * Interface definition for a callback to be invoked when the map is moved.
+ *
+ * @see MapboxMap#addOnMoveListener(OnMoveListener)
+ */
+ public interface OnMoveListener {
+ void onMoveBegin(MoveGestureDetector detector);
+
+ void onMove(MoveGestureDetector detector);
+
+ void onMoveEnd(MoveGestureDetector detector);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the map is rotated.
+ *
+ * @see MapboxMap#addOnRotateListener(OnRotateListener)
+ */
+ public interface OnRotateListener {
+ void onRotateBegin(RotateGestureDetector detector);
+
+ void onRotate(RotateGestureDetector detector);
+
+ void onRotateEnd(RotateGestureDetector detector);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the map is scaled.
+ *
+ * @see MapboxMap#addOnScaleListener(OnScaleListener)
+ */
+ public interface OnScaleListener {
+ void onScaleBegin(StandardScaleGestureDetector detector);
+
+ void onScale(StandardScaleGestureDetector detector);
+
+ void onScaleEnd(StandardScaleGestureDetector detector);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the map is tilted.
+ *
+ * @see MapboxMap#addOnShoveListener(OnShoveListener)
+ */
+ public interface OnShoveListener {
+ void onShoveBegin(ShoveGestureDetector detector);
+
+ void onShove(ShoveGestureDetector detector);
+
+ void onShoveEnd(ShoveGestureDetector detector);
+ }
+
+ /**
* Interface definition for a callback to be invoked when the camera changes position.
*
* @deprecated Replaced by {@link MapboxMap.OnCameraMoveStartedListener}, {@link MapboxMap.OnCameraMoveListener} and
@@ -2330,7 +2480,7 @@ public final class MapboxMap {
* Interface definition for a callback to be invoked when a user registers an listener that is
* related to touch and click events.
*/
- interface OnRegisterTouchListener {
+ interface OnGesturesManagerInteractionListener {
void onSetMapClickListener(OnMapClickListener listener);
void onAddMapClickListener(OnMapClickListener listener);
@@ -2354,6 +2504,26 @@ public final class MapboxMap {
void onAddFlingListener(OnFlingListener listener);
void onRemoveFlingListener(OnFlingListener listener);
+
+ void onAddMoveListener(OnMoveListener listener);
+
+ void onRemoveMoveListener(OnMoveListener listener);
+
+ void onAddRotateListener(OnRotateListener listener);
+
+ void onRemoveRotateListener(OnRotateListener listener);
+
+ void onAddScaleListener(OnScaleListener listener);
+
+ void onRemoveScaleListener(OnScaleListener listener);
+
+ void onAddShoveListener(OnShoveListener listener);
+
+ void onRemoveShoveListener(OnShoveListener listener);
+
+ AndroidGesturesManager getGesturesManager();
+
+ void setGesturesManager(AndroidGesturesManager gesturesManager);
}
/**
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java
index 84a601039f..43c943a16f 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java
@@ -205,6 +205,8 @@ final class Transform implements MapView.OnMapChangedListener {
// cancel ongoing transitions
mapView.cancelTransitions();
+
+ cameraChangeDispatcher.onCameraIdle();
}
@UiThread
@@ -235,39 +237,37 @@ final class Transform implements MapView.OnMapChangedListener {
return mapView.getZoom();
}
- void zoom(boolean zoomIn, @NonNull PointF focalPoint) {
+ void zoomIn(@NonNull PointF focalPoint) {
CameraPosition cameraPosition = invalidateCameraPosition();
if (cameraPosition != null) {
- int newZoom = (int) Math.round(cameraPosition.zoom + (zoomIn ? 1 : -1));
- setZoom(newZoom, focalPoint, MapboxConstants.ANIMATION_DURATION, false);
- } else {
- // we are not transforming, notify about being idle
- cameraChangeDispatcher.onCameraIdle();
+ int newZoom = (int) Math.round(cameraPosition.zoom + 1);
+ setZoom(newZoom, focalPoint, MapboxConstants.ANIMATION_DURATION);
}
}
- void zoom(double zoomAddition, @NonNull PointF focalPoint, long duration) {
+ void zoomOut(@NonNull PointF focalPoint) {
CameraPosition cameraPosition = invalidateCameraPosition();
if (cameraPosition != null) {
- int newZoom = (int) Math.round(cameraPosition.zoom + zoomAddition);
- setZoom(newZoom, focalPoint, duration, false);
- } else {
- // we are not transforming, notify about being idle
- cameraChangeDispatcher.onCameraIdle();
+ int newZoom = (int) Math.round(cameraPosition.zoom - 1);
+ setZoom(newZoom, focalPoint, MapboxConstants.ANIMATION_DURATION);
}
}
+ void zoomBy(double zoomAddition, @NonNull PointF focalPoint) {
+ setZoom(mapView.getZoom() + zoomAddition, focalPoint, 0);
+ }
+
void setZoom(double zoom, @NonNull PointF focalPoint) {
- setZoom(zoom, focalPoint, 0, false);
+ setZoom(zoom, focalPoint, 0);
}
- void setZoom(double zoom, @NonNull PointF focalPoint, long duration, final boolean isAnimator) {
+ void setZoom(double zoom, @NonNull PointF focalPoint, long duration) {
if (mapView != null) {
mapView.addOnMapChangedListener(new MapView.OnMapChangedListener() {
@Override
public void onMapChanged(int change) {
if (change == MapView.REGION_DID_CHANGE_ANIMATED) {
- if (!isAnimator) {
+ if (duration > 0) {
cameraChangeDispatcher.onCameraIdle();
}
mapView.removeOnMapChangedListener(this);
@@ -361,10 +361,6 @@ final class Transform implements MapView.OnMapChangedListener {
}
}
- void zoomBy(double z, float x, float y) {
- mapView.setZoom(mapView.getZoom() + z, new PointF(x, y), 0);
- }
-
void moveBy(double offsetX, double offsetY, long duration) {
if (duration > 0) {
mapView.addOnMapChangedListener(new MapView.OnMapChangedListener() {
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml
index 1c6a265587..00fc05cf6d 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml
@@ -6,4 +6,13 @@
<dimen name="mapbox_eight_dp">8dp</dimen>
<dimen name="mapbox_ninety_two_dp">92dp</dimen>
<dimen name="mapbox_my_locationview_outer_circle">18dp</dimen>
+
+ <!--Minimum scale velocity required to start animation-->
+ <dimen name="mapbox_minimum_scale_velocity">150dp</dimen>
+
+ <!--Minimum scale span delta required to execute scale gesture when rotating-->
+ <dimen name="mapbox_minimum_scale_span_when_rotating">100dp</dimen>
+
+ <!--Minimum angular velocity required to start rotation animation-->
+ <dimen name="mapbox_minimum_angular_velocity">0.025dp</dimen>
</resources>
diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapTouchListenersTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapTouchListenersTest.java
index eeb00355bd..5de55f47c9 100644
--- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapTouchListenersTest.java
+++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapTouchListenersTest.java
@@ -2,8 +2,13 @@ package com.mapbox.mapboxsdk.maps;
import android.graphics.PointF;
+import com.mapbox.android.gestures.MoveGestureDetector;
+import com.mapbox.android.gestures.RotateGestureDetector;
+import com.mapbox.android.gestures.ShoveGestureDetector;
+import com.mapbox.android.gestures.StandardScaleGestureDetector;
import com.mapbox.mapboxsdk.geometry.LatLng;
+import org.junit.Before;
import org.junit.Test;
import static org.mockito.Mockito.mock;
@@ -13,16 +18,23 @@ import static org.mockito.Mockito.when;
public class MapTouchListenersTest {
- @Test
- public void onMapClickListenerTest() throws Exception {
- LatLng latLng = new LatLng();
- PointF pointF = new PointF();
+ private MapGestureDetector mapGestureDetector;
+ private LatLng latLng;
+ private PointF pointF;
+
+ @Before
+ public void setUp() throws Exception {
+ latLng = new LatLng();
+ pointF = new PointF();
Projection projection = mock(Projection.class);
when(projection.fromScreenLocation(pointF)).thenReturn(latLng);
- MapGestureDetector mapGestureDetector = new MapGestureDetector(null,
- null, projection, null, null, null, null);
+ mapGestureDetector = new MapGestureDetector(null,
+ null, projection, null, null, null);
+ }
+ @Test
+ public void onMapClickListenerTest() throws Exception {
MapboxMap.OnMapClickListener listener = mock(MapboxMap.OnMapClickListener.class);
mapGestureDetector.addOnMapClickListener(listener);
mapGestureDetector.notifyOnMapClickListeners(pointF);
@@ -35,14 +47,6 @@ public class MapTouchListenersTest {
@Test
public void onMapLongClickListenerTest() throws Exception {
- LatLng latLng = new LatLng();
- PointF pointF = new PointF();
-
- Projection projection = mock(Projection.class);
- when(projection.fromScreenLocation(pointF)).thenReturn(latLng);
- MapGestureDetector mapGestureDetector = new MapGestureDetector(null,
- null, projection, null, null, null, null);
-
MapboxMap.OnMapLongClickListener listener = mock(MapboxMap.OnMapLongClickListener.class);
mapGestureDetector.addOnMapLongClickListener(listener);
mapGestureDetector.notifyOnMapLongClickListeners(pointF);
@@ -55,14 +59,6 @@ public class MapTouchListenersTest {
@Test
public void onFlingListenerTest() throws Exception {
- LatLng latLng = new LatLng();
- PointF pointF = new PointF();
-
- Projection projection = mock(Projection.class);
- when(projection.fromScreenLocation(pointF)).thenReturn(latLng);
- MapGestureDetector mapGestureDetector = new MapGestureDetector(null,
- null, projection, null, null, null, null);
-
MapboxMap.OnFlingListener listener = mock(MapboxMap.OnFlingListener.class);
mapGestureDetector.addOnFlingListener(listener);
mapGestureDetector.notifyOnFlingListeners();
@@ -75,14 +71,6 @@ public class MapTouchListenersTest {
@Test
public void onScrollListenerTest() throws Exception {
- LatLng latLng = new LatLng();
- PointF pointF = new PointF();
-
- Projection projection = mock(Projection.class);
- when(projection.fromScreenLocation(pointF)).thenReturn(latLng);
- MapGestureDetector mapGestureDetector = new MapGestureDetector(null,
- null, projection, null, null, null, null);
-
MapboxMap.OnScrollListener listener = mock(MapboxMap.OnScrollListener.class);
mapGestureDetector.addOnScrollListener(listener);
mapGestureDetector.notifyOnScrollListeners();
@@ -92,4 +80,88 @@ public class MapTouchListenersTest {
mapGestureDetector.notifyOnScrollListeners();
verify(listener, times(1)).onScroll();
}
+
+ @Test
+ public void onMoveListenerTest() throws Exception {
+ MapboxMap.OnMoveListener listener = mock(MapboxMap.OnMoveListener.class);
+ MoveGestureDetector detector = mock(MoveGestureDetector.class);
+ mapGestureDetector.addOnMoveListener(listener);
+ mapGestureDetector.notifyOnMoveBeginListeners(detector);
+ mapGestureDetector.notifyOnMoveListeners(detector);
+ mapGestureDetector.notifyOnMoveEndListeners(detector);
+ verify(listener, times(1)).onMoveBegin(detector);
+ verify(listener, times(1)).onMove(detector);
+ verify(listener, times(1)).onMoveEnd(detector);
+
+ mapGestureDetector.removeOnMoveListener(listener);
+ mapGestureDetector.notifyOnMoveBeginListeners(detector);
+ mapGestureDetector.notifyOnMoveListeners(detector);
+ mapGestureDetector.notifyOnMoveEndListeners(detector);
+ verify(listener, times(1)).onMoveBegin(detector);
+ verify(listener, times(1)).onMove(detector);
+ verify(listener, times(1)).onMoveEnd(detector);
+ }
+
+ @Test
+ public void onRotateListenerTest() throws Exception {
+ MapboxMap.OnRotateListener listener = mock(MapboxMap.OnRotateListener.class);
+ RotateGestureDetector detector = mock(RotateGestureDetector.class);
+ mapGestureDetector.addOnRotateListener(listener);
+ mapGestureDetector.notifyOnRotateBeginListeners(detector);
+ mapGestureDetector.notifyOnRotateListeners(detector);
+ mapGestureDetector.notifyOnRotateEndListeners(detector);
+ verify(listener, times(1)).onRotateBegin(detector);
+ verify(listener, times(1)).onRotate(detector);
+ verify(listener, times(1)).onRotateEnd(detector);
+
+ mapGestureDetector.removeOnRotateListener(listener);
+ mapGestureDetector.notifyOnRotateBeginListeners(detector);
+ mapGestureDetector.notifyOnRotateListeners(detector);
+ mapGestureDetector.notifyOnRotateEndListeners(detector);
+ verify(listener, times(1)).onRotateBegin(detector);
+ verify(listener, times(1)).onRotate(detector);
+ verify(listener, times(1)).onRotateEnd(detector);
+ }
+
+ @Test
+ public void onScaleListenerTest() throws Exception {
+ MapboxMap.OnScaleListener listener = mock(MapboxMap.OnScaleListener.class);
+ StandardScaleGestureDetector detector = mock(StandardScaleGestureDetector.class);
+ mapGestureDetector.addOnScaleListener(listener);
+ mapGestureDetector.notifyOnScaleBeginListeners(detector);
+ mapGestureDetector.notifyOnScaleListeners(detector);
+ mapGestureDetector.notifyOnScaleEndListeners(detector);
+ verify(listener, times(1)).onScaleBegin(detector);
+ verify(listener, times(1)).onScale(detector);
+ verify(listener, times(1)).onScaleEnd(detector);
+
+ mapGestureDetector.removeOnScaleListener(listener);
+ mapGestureDetector.notifyOnScaleBeginListeners(detector);
+ mapGestureDetector.notifyOnScaleListeners(detector);
+ mapGestureDetector.notifyOnScaleEndListeners(detector);
+ verify(listener, times(1)).onScaleBegin(detector);
+ verify(listener, times(1)).onScale(detector);
+ verify(listener, times(1)).onScaleEnd(detector);
+ }
+
+ @Test
+ public void onShoveListenerTest() throws Exception {
+ MapboxMap.OnShoveListener listener = mock(MapboxMap.OnShoveListener.class);
+ ShoveGestureDetector detector = mock(ShoveGestureDetector.class);
+ mapGestureDetector.addShoveListener(listener);
+ mapGestureDetector.notifyOnShoveBeginListeners(detector);
+ mapGestureDetector.notifyOnShoveListeners(detector);
+ mapGestureDetector.notifyOnShoveEndListeners(detector);
+ verify(listener, times(1)).onShoveBegin(detector);
+ verify(listener, times(1)).onShove(detector);
+ verify(listener, times(1)).onShoveEnd(detector);
+
+ mapGestureDetector.removeShoveListener(listener);
+ mapGestureDetector.notifyOnShoveBeginListeners(detector);
+ mapGestureDetector.notifyOnShoveListeners(detector);
+ mapGestureDetector.notifyOnShoveEndListeners(detector);
+ verify(listener, times(1)).onShoveBegin(detector);
+ verify(listener, times(1)).onShove(detector);
+ verify(listener, times(1)).onShoveEnd(detector);
+ }
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java
index 5e9f94db28..d61947f00e 100644
--- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java
+++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java
@@ -23,7 +23,7 @@ public class MapboxMapTest {
mock(TrackingSettings.class),
mock(MyLocationViewSettings.class),
mock(Projection.class),
- mock(MapboxMap.OnRegisterTouchListener.class),
+ mock(MapboxMap.OnGesturesManagerInteractionListener.class),
mock(AnnotationManager.class),
mock(CameraChangeDispatcher.class));
}
diff --git a/platform/android/gradle/dependencies.gradle b/platform/android/gradle/dependencies.gradle
index 4ef4ae2f7d..550f44ee26 100644
--- a/platform/android/gradle/dependencies.gradle
+++ b/platform/android/gradle/dependencies.gradle
@@ -8,24 +8,26 @@ ext {
]
versions = [
- mapboxServices: '3.0.0-beta.2',
+ mapboxServices : '3.0.0-beta.2',
mapboxTelemetry: '3.0.0-beta.1',
- supportLib : '25.4.0',
- espresso : '3.0.1',
- testRunner : '1.0.1',
- leakCanary : '1.5.1',
- lost : '3.0.4',
- junit : '4.12',
- mockito : '2.10.0',
- robolectric : '3.5.1',
- timber : '4.5.1',
- okhttp : '3.9.1'
+ mapboxGestures : '0.1.0-20180227.133736-12',
+ supportLib : '25.4.0',
+ espresso : '3.0.1',
+ testRunner : '1.0.1',
+ leakCanary : '1.5.1',
+ lost : '3.0.4',
+ junit : '4.12',
+ mockito : '2.10.0',
+ robolectric : '3.5.1',
+ timber : '4.5.1',
+ okhttp : '3.9.1'
]
dependenciesList = [
mapboxJavaServices : "com.mapbox.mapboxsdk:mapbox-sdk-services:${versions.mapboxServices}",
mapboxJavaGeoJSON : "com.mapbox.mapboxsdk:mapbox-sdk-geojson:${versions.mapboxServices}",
mapboxAndroidTelemetry: "com.mapbox.mapboxsdk:mapbox-android-telemetry:${versions.mapboxTelemetry}",
+ mapboxAndroidGestures : "com.mapbox.mapboxsdk:mapbox-android-gestures:${versions.mapboxGestures}@aar",
// for testApp
mapboxJavaTurf : "com.mapbox.mapboxsdk:mapbox-sdk-turf:${versions.mapboxServices}",