[android ] - new gestures library (#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
This commit is contained in:
Łukasz Paczos
2018-02-27 15:51:22 +01:00
committed by GitHub
parent 380f5dc88b
commit 7905bd6831
17 changed files with 1073 additions and 1696 deletions

View File

@@ -3,6 +3,7 @@ apply plugin: 'com.android.library'
dependencies { dependencies {
api dependenciesList.mapboxAndroidTelemetry api dependenciesList.mapboxAndroidTelemetry
api dependenciesList.mapboxJavaGeoJSON api dependenciesList.mapboxJavaGeoJSON
api dependenciesList.mapboxAndroidGestures
implementation dependenciesList.supportAnnotations implementation dependenciesList.supportAnnotations
implementation dependenciesList.supportFragmentV4 implementation dependenciesList.supportFragmentV4
implementation dependenciesList.timber implementation dependenciesList.timber

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -1,4 +0,0 @@
/**
* Do not use this package. Internal use only.
*/
package com.almeros.android.multitouch.gesturedetectors;

View File

@@ -47,6 +47,31 @@ public class MapboxConstants {
*/ */
public static final long VELOCITY_THRESHOLD_IGNORE_FLING = 1000; 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. * The currently supported minimum zoom level.
*/ */
@@ -78,14 +103,14 @@ public class MapboxConstants {
public static final double MINIMUM_DIRECTION = 0; 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; 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 * 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 * Fragment Argument Key for MapboxMapOptions

View File

@@ -128,7 +128,7 @@ final class MapKeyListener {
// Zoom out // Zoom out
PointF focalPoint = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2); PointF focalPoint = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
transform.zoom(false, focalPoint); transform.zoomOut(focalPoint);
return true; return true;
default: default:
@@ -164,7 +164,7 @@ final class MapKeyListener {
// Zoom in // Zoom in
PointF focalPoint = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2); PointF focalPoint = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
transform.zoom(true, focalPoint); transform.zoomIn(focalPoint);
return true; return true;
} }
@@ -219,7 +219,7 @@ final class MapKeyListener {
if (currentTrackballLongPressTimeOut != null) { if (currentTrackballLongPressTimeOut != null) {
// Zoom in // Zoom in
PointF focalPoint = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2); PointF focalPoint = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
transform.zoom(true, focalPoint); transform.zoomIn(focalPoint);
} }
return true; return true;
@@ -261,7 +261,7 @@ final class MapKeyListener {
if (!cancelled) { if (!cancelled) {
// Zoom out // Zoom out
PointF pointF = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2); PointF pointF = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
transform.zoom(false, pointF); transform.zoomOut(pointF);
// Ensure the up action is not run // Ensure the up action is not run
currentTrackballLongPressTimeOut = null; currentTrackballLongPressTimeOut = null;

View File

@@ -23,6 +23,7 @@ import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ZoomButtonsController; import android.widget.ZoomButtonsController;
import com.mapbox.android.gestures.AndroidGesturesManager;
import com.mapbox.android.telemetry.AppUserTurnstile; import com.mapbox.android.telemetry.AppUserTurnstile;
import com.mapbox.android.telemetry.Event; import com.mapbox.android.telemetry.Event;
import com.mapbox.android.telemetry.MapEventFactory; 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.net.ConnectivityReceiver;
import com.mapbox.mapboxsdk.storage.FileSource; 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.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
@@ -52,6 +51,9 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import timber.log.Timber; import timber.log.Timber;
import static com.mapbox.mapboxsdk.maps.widgets.CompassView.TIME_MAP_NORTH_ANIMATION; import static com.mapbox.mapboxsdk.maps.widgets.CompassView.TIME_MAP_NORTH_ANIMATION;
@@ -149,7 +151,7 @@ public class MapView extends FrameLayout {
focalPointInvalidator.addListener(createFocalPointChangeListener()); focalPointInvalidator.addListener(createFocalPointChangeListener());
// callback for registering touch listeners // callback for registering touch listeners
RegisterTouchListener registerTouchListener = new RegisterTouchListener(); GesturesManagerInteractionListener registerTouchListener = new GesturesManagerInteractionListener();
// callback for zooming in the camera // callback for zooming in the camera
CameraZoomInvalidator zoomInvalidator = new CameraZoomInvalidator(); CameraZoomInvalidator zoomInvalidator = new CameraZoomInvalidator();
@@ -184,7 +186,7 @@ public class MapView extends FrameLayout {
mapCallback.attachMapboxMap(mapboxMap); mapCallback.attachMapboxMap(mapboxMap);
// user input // user input
mapGestureDetector = new MapGestureDetector(context, transform, proj, uiSettings, trackingSettings, mapGestureDetector = new MapGestureDetector(context, transform, proj, uiSettings,
annotationManager, cameraChangeDispatcher); annotationManager, cameraChangeDispatcher);
mapKeyListener = new MapKeyListener(transform, trackingSettings, uiSettings); mapKeyListener = new MapKeyListener(transform, trackingSettings, uiSettings);
@@ -388,6 +390,7 @@ public class MapView extends FrameLayout {
public void onStop() { public void onStop() {
if (mapboxMap != null) { if (mapboxMap != null) {
// map was destroyed before it was started // map was destroyed before it was started
mapGestureDetector.cancelAnimators();
mapboxMap.onStop(); mapboxMap.onStop();
} }
@@ -921,7 +924,7 @@ public class MapView extends FrameLayout {
} }
} }
private class RegisterTouchListener implements MapboxMap.OnRegisterTouchListener { private class GesturesManagerInteractionListener implements MapboxMap.OnGesturesManagerInteractionListener {
@Override @Override
public void onSetMapClickListener(MapboxMap.OnMapClickListener listener) { public void onSetMapClickListener(MapboxMap.OnMapClickListener listener) {
@@ -982,6 +985,56 @@ public class MapView extends FrameLayout {
public void onRemoveFlingListener(MapboxMap.OnFlingListener listener) { public void onRemoveFlingListener(MapboxMap.OnFlingListener listener) {
mapGestureDetector.removeOnFlingListener(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 { private static class MapZoomControllerListener implements ZoomButtonsController.OnZoomListener {
@@ -1019,11 +1072,13 @@ public class MapView extends FrameLayout {
} }
private void onZoom(boolean zoomIn, @Nullable PointF focalPoint) { private void onZoom(boolean zoomIn, @Nullable PointF focalPoint) {
if (focalPoint != null) { if (focalPoint == null) {
transform.zoom(zoomIn, focalPoint); focalPoint = new PointF(mapWidth / 2, mapHeight / 2);
}
if (zoomIn) {
transform.zoomIn(focalPoint);
} else { } else {
PointF centerPoint = new PointF(mapWidth / 2, mapHeight / 2); transform.zoomOut(focalPoint);
transform.zoom(zoomIn, centerPoint);
} }
} }
} }

View File

@@ -16,6 +16,12 @@ import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.Feature;
import com.mapbox.geojson.Geometry; import com.mapbox.geojson.Geometry;
import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.Annotation;
@@ -43,7 +49,6 @@ import com.mapbox.mapboxsdk.style.layers.Filter;
import com.mapbox.mapboxsdk.style.layers.Layer; import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.style.light.Light; import com.mapbox.mapboxsdk.style.light.Light;
import com.mapbox.mapboxsdk.style.sources.Source; import com.mapbox.mapboxsdk.style.sources.Source;
import com.mapbox.android.core.location.LocationEngine;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.util.HashMap; import java.util.HashMap;
@@ -73,13 +78,13 @@ public final class MapboxMap {
private final MyLocationViewSettings myLocationViewSettings; private final MyLocationViewSettings myLocationViewSettings;
private final CameraChangeDispatcher cameraChangeDispatcher; private final CameraChangeDispatcher cameraChangeDispatcher;
private final OnRegisterTouchListener onRegisterTouchListener; private final OnGesturesManagerInteractionListener onGesturesManagerInteractionListener;
private MapboxMap.OnFpsChangedListener onFpsChangedListener; private MapboxMap.OnFpsChangedListener onFpsChangedListener;
private PointF focalPoint; private PointF focalPoint;
MapboxMap(NativeMapView map, Transform transform, UiSettings ui, TrackingSettings tracking, 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) { AnnotationManager annotations, CameraChangeDispatcher cameraChangeDispatcher) {
this.nativeMapView = map; this.nativeMapView = map;
this.uiSettings = ui; this.uiSettings = ui;
@@ -88,7 +93,7 @@ public final class MapboxMap {
this.myLocationViewSettings = myLocationView; this.myLocationViewSettings = myLocationView;
this.annotationManager = annotations.bind(this); this.annotationManager = annotations.bind(this);
this.transform = transform; this.transform = transform;
this.onRegisterTouchListener = listener; this.onGesturesManagerInteractionListener = listener;
this.cameraChangeDispatcher = cameraChangeDispatcher; this.cameraChangeDispatcher = cameraChangeDispatcher;
} }
@@ -1882,12 +1887,11 @@ public final class MapboxMap {
* *
* @param listener The callback that's invoked when the map is scrolled. * @param listener The callback that's invoked when the map is scrolled.
* To unset the callback, use null. * To unset the callback, use null.
*
* @deprecated Use {@link #addOnScrollListener(OnScrollListener)} instead. * @deprecated Use {@link #addOnScrollListener(OnScrollListener)} instead.
*/ */
@Deprecated @Deprecated
public void setOnScrollListener(@Nullable OnScrollListener listener) { public void setOnScrollListener(@Nullable OnScrollListener listener) {
onRegisterTouchListener.onSetScrollListener(listener); onGesturesManagerInteractionListener.onSetScrollListener(listener);
} }
/** /**
@@ -1895,10 +1899,9 @@ public final class MapboxMap {
* *
* @param listener The callback that's invoked when the map is scrolled. * @param listener The callback that's invoked when the map is scrolled.
* To unset the callback, use null. * To unset the callback, use null.
*
*/ */
public void addOnScrollListener(@Nullable OnScrollListener listener) { public void addOnScrollListener(@Nullable OnScrollListener listener) {
onRegisterTouchListener.onAddScrollListener(listener); onGesturesManagerInteractionListener.onAddScrollListener(listener);
} }
/** /**
@@ -1906,10 +1909,9 @@ public final class MapboxMap {
* *
* @param listener The callback that's invoked when the map is scrolled. * @param listener The callback that's invoked when the map is scrolled.
* To unset the callback, use null. * To unset the callback, use null.
*
*/ */
public void removeOnScrollListener(@Nullable OnScrollListener listener) { public void removeOnScrollListener(@Nullable OnScrollListener listener) {
onRegisterTouchListener.onRemoveScrollListener(listener); onGesturesManagerInteractionListener.onRemoveScrollListener(listener);
} }
/** /**
@@ -1917,12 +1919,11 @@ public final class MapboxMap {
* *
* @param listener The callback that's invoked when the map is flinged. * @param listener The callback that's invoked when the map is flinged.
* To unset the callback, use null. * To unset the callback, use null.
*
* @deprecated Use {@link #addOnFlingListener(OnFlingListener)} instead. * @deprecated Use {@link #addOnFlingListener(OnFlingListener)} instead.
*/ */
@Deprecated @Deprecated
public void setOnFlingListener(@Nullable OnFlingListener listener) { public void setOnFlingListener(@Nullable OnFlingListener listener) {
onRegisterTouchListener.onSetFlingListener(listener); onGesturesManagerInteractionListener.onSetFlingListener(listener);
} }
/** /**
@@ -1932,7 +1933,7 @@ public final class MapboxMap {
* To unset the callback, use null. * To unset the callback, use null.
*/ */
public void addOnFlingListener(@Nullable OnFlingListener listener) { public void addOnFlingListener(@Nullable OnFlingListener listener) {
onRegisterTouchListener.onAddFlingListener(listener); onGesturesManagerInteractionListener.onAddFlingListener(listener);
} }
/** /**
@@ -1942,7 +1943,98 @@ public final class MapboxMap {
* To unset the callback, use null. * To unset the callback, use null.
*/ */
public void removeOnFlingListener(@Nullable OnFlingListener listener) { 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();
} }
/** /**
@@ -1950,12 +2042,11 @@ public final class MapboxMap {
* *
* @param listener The callback that's invoked when the user clicks on the map view. * @param listener The callback that's invoked when the user clicks on the map view.
* To unset the callback, use null. * To unset the callback, use null.
*
* @deprecated Use {@link #addOnMapClickListener(OnMapClickListener)} instead. * @deprecated Use {@link #addOnMapClickListener(OnMapClickListener)} instead.
*/ */
@Deprecated @Deprecated
public void setOnMapClickListener(@Nullable OnMapClickListener listener) { public void setOnMapClickListener(@Nullable OnMapClickListener listener) {
onRegisterTouchListener.onSetMapClickListener(listener); onGesturesManagerInteractionListener.onSetMapClickListener(listener);
} }
/** /**
@@ -1965,7 +2056,7 @@ public final class MapboxMap {
* To unset the callback, use null. * To unset the callback, use null.
*/ */
public void addOnMapClickListener(@Nullable OnMapClickListener listener) { public void addOnMapClickListener(@Nullable OnMapClickListener listener) {
onRegisterTouchListener.onAddMapClickListener(listener); onGesturesManagerInteractionListener.onAddMapClickListener(listener);
} }
/** /**
@@ -1975,7 +2066,7 @@ public final class MapboxMap {
* To unset the callback, use null. * To unset the callback, use null.
*/ */
public void removeOnMapClickListener(@Nullable OnMapClickListener listener) { public void removeOnMapClickListener(@Nullable OnMapClickListener listener) {
onRegisterTouchListener.onRemoveMapClickListener(listener); onGesturesManagerInteractionListener.onRemoveMapClickListener(listener);
} }
/** /**
@@ -1983,12 +2074,11 @@ public final class MapboxMap {
* *
* @param listener The callback that's invoked when the user long clicks on the map view. * @param listener The callback that's invoked when the user long clicks on the map view.
* To unset the callback, use null. * To unset the callback, use null.
*
* @deprecated Use {@link #addOnMapLongClickListener(OnMapLongClickListener)} instead. * @deprecated Use {@link #addOnMapLongClickListener(OnMapLongClickListener)} instead.
*/ */
@Deprecated @Deprecated
public void setOnMapLongClickListener(@Nullable OnMapLongClickListener listener) { public void setOnMapLongClickListener(@Nullable OnMapLongClickListener listener) {
onRegisterTouchListener.onSetMapLongClickListener(listener); onGesturesManagerInteractionListener.onSetMapLongClickListener(listener);
} }
/** /**
@@ -1998,7 +2088,7 @@ public final class MapboxMap {
* To unset the callback, use null. * To unset the callback, use null.
*/ */
public void addOnMapLongClickListener(@Nullable OnMapLongClickListener listener) { public void addOnMapLongClickListener(@Nullable OnMapLongClickListener listener) {
onRegisterTouchListener.onAddMapLongClickListener(listener); onGesturesManagerInteractionListener.onAddMapLongClickListener(listener);
} }
/** /**
@@ -2008,7 +2098,7 @@ public final class MapboxMap {
* To unset the callback, use null. * To unset the callback, use null.
*/ */
public void removeOnMapLongClickListener(@Nullable OnMapLongClickListener listener) { public void removeOnMapLongClickListener(@Nullable OnMapLongClickListener listener) {
onRegisterTouchListener.onRemoveMapLongClickListener(listener); onGesturesManagerInteractionListener.onRemoveMapLongClickListener(listener);
} }
/** /**
@@ -2267,7 +2357,9 @@ public final class MapboxMap {
* Interface definition for a callback to be invoked when the map is scrolled. * Interface definition for a callback to be invoked when the map is scrolled.
* *
* @see MapboxMap#setOnScrollListener(OnScrollListener) * @see MapboxMap#setOnScrollListener(OnScrollListener)
* @deprecated Use {@link OnMoveListener} instead.
*/ */
@Deprecated
public interface OnScrollListener { public interface OnScrollListener {
/** /**
* Called when the map is scrolled. * Called when the map is scrolled.
@@ -2275,6 +2367,58 @@ public final class MapboxMap {
void onScroll(); void onScroll();
} }
/**
* 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. * Interface definition for a callback to be invoked when the camera changes position.
* *
@@ -2377,7 +2521,7 @@ public final class MapboxMap {
* Interface definition for a callback to be invoked when a user registers an listener that is * Interface definition for a callback to be invoked when a user registers an listener that is
* related to touch and click events. * related to touch and click events.
*/ */
interface OnRegisterTouchListener { interface OnGesturesManagerInteractionListener {
void onSetMapClickListener(OnMapClickListener listener); void onSetMapClickListener(OnMapClickListener listener);
void onAddMapClickListener(OnMapClickListener listener); void onAddMapClickListener(OnMapClickListener listener);
@@ -2401,6 +2545,26 @@ public final class MapboxMap {
void onAddFlingListener(OnFlingListener listener); void onAddFlingListener(OnFlingListener listener);
void onRemoveFlingListener(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);
} }
/** /**

View File

@@ -205,6 +205,8 @@ final class Transform implements MapView.OnMapChangedListener {
// cancel ongoing transitions // cancel ongoing transitions
mapView.cancelTransitions(); mapView.cancelTransitions();
cameraChangeDispatcher.onCameraIdle();
} }
@UiThread @UiThread
@@ -235,39 +237,37 @@ final class Transform implements MapView.OnMapChangedListener {
return mapView.getZoom(); return mapView.getZoom();
} }
void zoom(boolean zoomIn, @NonNull PointF focalPoint) { void zoomIn(@NonNull PointF focalPoint) {
CameraPosition cameraPosition = invalidateCameraPosition(); CameraPosition cameraPosition = invalidateCameraPosition();
if (cameraPosition != null) { if (cameraPosition != null) {
int newZoom = (int) Math.round(cameraPosition.zoom + (zoomIn ? 1 : -1)); int newZoom = (int) Math.round(cameraPosition.zoom + 1);
setZoom(newZoom, focalPoint, MapboxConstants.ANIMATION_DURATION, false); setZoom(newZoom, focalPoint, MapboxConstants.ANIMATION_DURATION);
} else {
// we are not transforming, notify about being idle
cameraChangeDispatcher.onCameraIdle();
} }
} }
void zoom(double zoomAddition, @NonNull PointF focalPoint, long duration) { void zoomOut(@NonNull PointF focalPoint) {
CameraPosition cameraPosition = invalidateCameraPosition(); CameraPosition cameraPosition = invalidateCameraPosition();
if (cameraPosition != null) { if (cameraPosition != null) {
int newZoom = (int) Math.round(cameraPosition.zoom + zoomAddition); int newZoom = (int) Math.round(cameraPosition.zoom - 1);
setZoom(newZoom, focalPoint, duration, false); setZoom(newZoom, focalPoint, MapboxConstants.ANIMATION_DURATION);
} else {
// we are not transforming, notify about being idle
cameraChangeDispatcher.onCameraIdle();
} }
} }
void zoomBy(double zoomAddition, @NonNull PointF focalPoint) {
setZoom(mapView.getZoom() + zoomAddition, focalPoint, 0);
}
void setZoom(double zoom, @NonNull PointF focalPoint) { 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) { if (mapView != null) {
mapView.addOnMapChangedListener(new MapView.OnMapChangedListener() { mapView.addOnMapChangedListener(new MapView.OnMapChangedListener() {
@Override @Override
public void onMapChanged(int change) { public void onMapChanged(int change) {
if (change == MapView.REGION_DID_CHANGE_ANIMATED) { if (change == MapView.REGION_DID_CHANGE_ANIMATED) {
if (!isAnimator) { if (duration > 0) {
cameraChangeDispatcher.onCameraIdle(); cameraChangeDispatcher.onCameraIdle();
} }
mapView.removeOnMapChangedListener(this); 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) { void moveBy(double offsetX, double offsetY, long duration) {
if (duration > 0) { if (duration > 0) {
mapView.addOnMapChangedListener(new MapView.OnMapChangedListener() { mapView.addOnMapChangedListener(new MapView.OnMapChangedListener() {

View File

@@ -6,4 +6,13 @@
<dimen name="mapbox_eight_dp">8dp</dimen> <dimen name="mapbox_eight_dp">8dp</dimen>
<dimen name="mapbox_ninety_two_dp">92dp</dimen> <dimen name="mapbox_ninety_two_dp">92dp</dimen>
<dimen name="mapbox_my_locationview_outer_circle">18dp</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> </resources>

View File

@@ -2,8 +2,13 @@ package com.mapbox.mapboxsdk.maps;
import android.graphics.PointF; 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 com.mapbox.mapboxsdk.geometry.LatLng;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@@ -13,16 +18,23 @@ import static org.mockito.Mockito.when;
public class MapTouchListenersTest { public class MapTouchListenersTest {
@Test private MapGestureDetector mapGestureDetector;
public void onMapClickListenerTest() throws Exception { private LatLng latLng;
LatLng latLng = new LatLng(); private PointF pointF;
PointF pointF = new PointF();
@Before
public void setUp() throws Exception {
latLng = new LatLng();
pointF = new PointF();
Projection projection = mock(Projection.class); Projection projection = mock(Projection.class);
when(projection.fromScreenLocation(pointF)).thenReturn(latLng); when(projection.fromScreenLocation(pointF)).thenReturn(latLng);
MapGestureDetector mapGestureDetector = new MapGestureDetector(null, mapGestureDetector = new MapGestureDetector(null,
null, projection, null, null, null, null); null, projection, null, null, null);
}
@Test
public void onMapClickListenerTest() throws Exception {
MapboxMap.OnMapClickListener listener = mock(MapboxMap.OnMapClickListener.class); MapboxMap.OnMapClickListener listener = mock(MapboxMap.OnMapClickListener.class);
mapGestureDetector.addOnMapClickListener(listener); mapGestureDetector.addOnMapClickListener(listener);
mapGestureDetector.notifyOnMapClickListeners(pointF); mapGestureDetector.notifyOnMapClickListeners(pointF);
@@ -35,14 +47,6 @@ public class MapTouchListenersTest {
@Test @Test
public void onMapLongClickListenerTest() throws Exception { 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); MapboxMap.OnMapLongClickListener listener = mock(MapboxMap.OnMapLongClickListener.class);
mapGestureDetector.addOnMapLongClickListener(listener); mapGestureDetector.addOnMapLongClickListener(listener);
mapGestureDetector.notifyOnMapLongClickListeners(pointF); mapGestureDetector.notifyOnMapLongClickListeners(pointF);
@@ -55,14 +59,6 @@ public class MapTouchListenersTest {
@Test @Test
public void onFlingListenerTest() throws Exception { 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); MapboxMap.OnFlingListener listener = mock(MapboxMap.OnFlingListener.class);
mapGestureDetector.addOnFlingListener(listener); mapGestureDetector.addOnFlingListener(listener);
mapGestureDetector.notifyOnFlingListeners(); mapGestureDetector.notifyOnFlingListeners();
@@ -75,14 +71,6 @@ public class MapTouchListenersTest {
@Test @Test
public void onScrollListenerTest() throws Exception { 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); MapboxMap.OnScrollListener listener = mock(MapboxMap.OnScrollListener.class);
mapGestureDetector.addOnScrollListener(listener); mapGestureDetector.addOnScrollListener(listener);
mapGestureDetector.notifyOnScrollListeners(); mapGestureDetector.notifyOnScrollListeners();
@@ -92,4 +80,88 @@ public class MapTouchListenersTest {
mapGestureDetector.notifyOnScrollListeners(); mapGestureDetector.notifyOnScrollListeners();
verify(listener, times(1)).onScroll(); 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);
}
} }

View File

@@ -23,7 +23,7 @@ public class MapboxMapTest {
mock(TrackingSettings.class), mock(TrackingSettings.class),
mock(MyLocationViewSettings.class), mock(MyLocationViewSettings.class),
mock(Projection.class), mock(Projection.class),
mock(MapboxMap.OnRegisterTouchListener.class), mock(MapboxMap.OnGesturesManagerInteractionListener.class),
mock(AnnotationManager.class), mock(AnnotationManager.class),
mock(CameraChangeDispatcher.class)); mock(CameraChangeDispatcher.class));
} }

View File

@@ -8,24 +8,26 @@ ext {
] ]
versions = [ versions = [
mapboxServices: '3.0.0-beta.2', mapboxServices : '3.0.0-beta.2',
mapboxTelemetry: '3.0.0-beta.1', mapboxTelemetry: '3.0.0-beta.1',
supportLib : '25.4.0', mapboxGestures : '0.1.0-20180227.133736-12',
espresso : '3.0.1', supportLib : '25.4.0',
testRunner : '1.0.1', espresso : '3.0.1',
leakCanary : '1.5.1', testRunner : '1.0.1',
lost : '3.0.4', leakCanary : '1.5.1',
junit : '4.12', lost : '3.0.4',
mockito : '2.10.0', junit : '4.12',
robolectric : '3.5.1', mockito : '2.10.0',
timber : '4.5.1', robolectric : '3.5.1',
okhttp : '3.9.1' timber : '4.5.1',
okhttp : '3.9.1'
] ]
dependenciesList = [ dependenciesList = [
mapboxJavaServices : "com.mapbox.mapboxsdk:mapbox-sdk-services:${versions.mapboxServices}", mapboxJavaServices : "com.mapbox.mapboxsdk:mapbox-sdk-services:${versions.mapboxServices}",
mapboxJavaGeoJSON : "com.mapbox.mapboxsdk:mapbox-sdk-geojson:${versions.mapboxServices}", mapboxJavaGeoJSON : "com.mapbox.mapboxsdk:mapbox-sdk-geojson:${versions.mapboxServices}",
mapboxAndroidTelemetry: "com.mapbox.mapboxsdk:mapbox-android-telemetry:${versions.mapboxTelemetry}", mapboxAndroidTelemetry: "com.mapbox.mapboxsdk:mapbox-android-telemetry:${versions.mapboxTelemetry}",
mapboxAndroidGestures : "com.mapbox.mapboxsdk:mapbox-android-gestures:${versions.mapboxGestures}@aar",
// for testApp // for testApp
mapboxJavaTurf : "com.mapbox.mapboxsdk:mapbox-sdk-turf:${versions.mapboxServices}", mapboxJavaTurf : "com.mapbox.mapboxsdk:mapbox-sdk-turf:${versions.mapboxServices}",