[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 {
api dependenciesList.mapboxAndroidTelemetry
api dependenciesList.mapboxJavaGeoJSON
api dependenciesList.mapboxAndroidGestures
implementation dependenciesList.supportAnnotations
implementation dependenciesList.supportFragmentV4
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;
/**
* 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.
*/
@@ -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

View File

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

View File

@@ -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);
@@ -388,6 +390,7 @@ public class MapView extends FrameLayout {
public void onStop() {
if (mapboxMap != null) {
// map was destroyed before it was started
mapGestureDetector.cancelAnimators();
mapboxMap.onStop();
}
@@ -921,7 +924,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) {
@@ -982,6 +985,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 {
@@ -1019,11 +1072,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);
}
}
}

View File

@@ -16,6 +16,12 @@ import android.text.TextUtils;
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;
@@ -43,7 +49,6 @@ import com.mapbox.mapboxsdk.style.layers.Filter;
import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.style.light.Light;
import com.mapbox.mapboxsdk.style.sources.Source;
import com.mapbox.android.core.location.LocationEngine;
import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
@@ -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;
}
@@ -1882,12 +1887,11 @@ public final class MapboxMap {
*
* @param listener The callback that's invoked when the map is scrolled.
* To unset the callback, use null.
*
* @deprecated Use {@link #addOnScrollListener(OnScrollListener)} instead.
*/
@Deprecated
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.
* To unset the callback, use null.
*
*/
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.
* To unset the callback, use null.
*
*/
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.
* To unset the callback, use null.
*
* @deprecated Use {@link #addOnFlingListener(OnFlingListener)} instead.
*/
@Deprecated
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.
*/
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.
*/
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.
* To unset the callback, use null.
*
* @deprecated Use {@link #addOnMapClickListener(OnMapClickListener)} instead.
*/
@Deprecated
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.
*/
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.
*/
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.
* To unset the callback, use null.
*
* @deprecated Use {@link #addOnMapLongClickListener(OnMapLongClickListener)} instead.
*/
@Deprecated
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.
*/
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.
*/
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.
*
* @see MapboxMap#setOnScrollListener(OnScrollListener)
* @deprecated Use {@link OnMoveListener} instead.
*/
@Deprecated
public interface OnScrollListener {
/**
* Called when the map is scrolled.
@@ -2275,6 +2367,58 @@ public final class MapboxMap {
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.
*
@@ -2377,7 +2521,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);
@@ -2401,6 +2545,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);
}
/**

View File

@@ -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() {

View File

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

View File

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

View File

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

View File

@@ -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}",