summaryrefslogtreecommitdiff
path: root/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java')
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java512
1 files changed, 387 insertions, 125 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java
index 33e13c5ecc..0fea5ce0ff 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java
@@ -1,15 +1,19 @@
package com.mapbox.mapboxsdk.maps;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.PointF;
import android.location.Location;
-import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.ScaleGestureDetectorCompat;
+import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
+import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector;
@@ -22,6 +26,8 @@ import com.mapbox.services.android.telemetry.MapboxTelemetry;
import com.mapbox.services.android.telemetry.utils.MathUtils;
import com.mapbox.services.android.telemetry.utils.TelemetryUtils;
+import java.util.concurrent.CopyOnWriteArrayList;
+
import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE;
/**
@@ -39,24 +45,46 @@ final class MapGestureDetector {
private final AnnotationManager annotationManager;
private final CameraChangeDispatcher cameraChangeDispatcher;
- private final GestureDetectorCompat gestureDetector;
- private final ScaleGestureDetector scaleGestureDetector;
- private final RotateGestureDetector rotateGestureDetector;
- private final ShoveGestureDetector shoveGestureDetector;
+ private GestureDetectorCompat gestureDetector;
+ private ScaleGestureDetector scaleGestureDetector;
+ private RotateGestureDetector rotateGestureDetector;
+ private ShoveGestureDetector shoveGestureDetector;
+ // deprecated map touch API
private MapboxMap.OnMapClickListener onMapClickListener;
private MapboxMap.OnMapLongClickListener onMapLongClickListener;
private MapboxMap.OnFlingListener onFlingListener;
private MapboxMap.OnScrollListener onScrollListener;
+ // new map touch API
+ private final CopyOnWriteArrayList<MapboxMap.OnMapClickListener> onMapClickListenerList
+ = new CopyOnWriteArrayList<>();
+
+ private final CopyOnWriteArrayList<MapboxMap.OnMapLongClickListener> onMapLongClickListenerList
+ = new CopyOnWriteArrayList<>();
+
+ private final CopyOnWriteArrayList<MapboxMap.OnFlingListener> onFlingListenerList
+ = new CopyOnWriteArrayList<>();
+
+ private final CopyOnWriteArrayList<MapboxMap.OnScrollListener> onScrollListenerList
+ = new CopyOnWriteArrayList<>();
+
private PointF focalPoint;
- private boolean twoTap = false;
- private boolean zoomStarted = false;
- private boolean dragStarted = false;
- private boolean quickZoom = false;
- private boolean scrollInProgress = false;
- private boolean scaleGestureOccurred = false;
+ private boolean twoTap;
+ private boolean quickZoom;
+ private boolean tiltGestureOccurred;
+ private boolean scrollGestureOccurred;
+
+ private boolean scaleGestureOccurred;
+ private boolean recentScaleGestureOccurred;
+ private boolean scaleAnimating;
+ private long scaleBeginTime;
+
+ private VelocityTracker velocityTracker;
+ private boolean wasZoomingIn;
+ private boolean wasClockwiseRotating;
+ private boolean rotateGestureOccurred;
MapGestureDetector(Context context, Transform transform, Projection projection, UiSettings uiSettings,
TrackingSettings trackingSettings, AnnotationManager annotationManager,
@@ -69,12 +97,14 @@ final class MapGestureDetector {
this.cameraChangeDispatcher = cameraChangeDispatcher;
// Touch gesture detectors
- gestureDetector = new GestureDetectorCompat(context, new GestureListener());
- gestureDetector.setIsLongpressEnabled(true);
- scaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener());
- ScaleGestureDetectorCompat.setQuickScaleEnabled(scaleGestureDetector, true);
- rotateGestureDetector = new RotateGestureDetector(context, new RotateGestureListener());
- shoveGestureDetector = new ShoveGestureDetector(context, new ShoveGestureListener());
+ if (context != null) {
+ gestureDetector = new GestureDetectorCompat(context, new GestureListener());
+ gestureDetector.setIsLongpressEnabled(true);
+ scaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener());
+ ScaleGestureDetectorCompat.setQuickScaleEnabled(scaleGestureDetector, true);
+ rotateGestureDetector = new RotateGestureDetector(context, new RotateGestureListener());
+ shoveGestureDetector = new ShoveGestureDetector(context, new ShoveGestureListener());
+ }
}
/**
@@ -133,22 +163,33 @@ final class MapGestureDetector {
* @param event the MotionEvent
* @return True if touch event is handled
*/
- boolean onTouchEvent(@NonNull MotionEvent event) {
+ boolean onTouchEvent(MotionEvent event) {
+ // framework can return null motion events in edge cases #9432
+ if (event == null) {
+ return false;
+ }
+
// Check and ignore non touch or left clicks
if ((event.getButtonState() != 0) && (event.getButtonState() != MotionEvent.BUTTON_PRIMARY)) {
return false;
}
// Check two finger gestures first
- rotateGestureDetector.onTouchEvent(event);
scaleGestureDetector.onTouchEvent(event);
+ rotateGestureDetector.onTouchEvent(event);
shoveGestureDetector.onTouchEvent(event);
// Handle two finger tap
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
+ if (velocityTracker == null) {
+ velocityTracker = VelocityTracker.obtain();
+ } else {
+ velocityTracker.clear();
+ }
+ velocityTracker.addMovement(event);
// First pointer down, reset scaleGestureOccurred, used to avoid triggering a fling after a scale gesture #7666
- scaleGestureOccurred = false;
+ recentScaleGestureOccurred = false;
transform.setGestureInProgress(true);
break;
@@ -188,20 +229,34 @@ final class MapGestureDetector {
}
// Scroll / Pan Has Stopped
- if (scrollInProgress) {
+ if (scrollGestureOccurred) {
MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapDragEndEvent(
getLocationFromGesture(event.getX(), event.getY()), transform));
- scrollInProgress = false;
+ scrollGestureOccurred = false;
cameraChangeDispatcher.onCameraIdle();
}
twoTap = false;
transform.setGestureInProgress(false);
+ if (velocityTracker != null) {
+ velocityTracker.recycle();
+ }
+ velocityTracker = null;
break;
case MotionEvent.ACTION_CANCEL:
twoTap = false;
transform.setGestureInProgress(false);
+ if (velocityTracker != null) {
+ velocityTracker.recycle();
+ }
+ velocityTracker = null;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (velocityTracker != null) {
+ velocityTracker.addMovement(event);
+ velocityTracker.computeCurrentVelocity(1000);
+ }
break;
}
@@ -250,7 +305,6 @@ final class MapGestureDetector {
return false;
}
-
/**
* Responsible for handling one finger gestures.
*/
@@ -274,7 +328,7 @@ final class MapGestureDetector {
break;
case MotionEvent.ACTION_UP:
if (quickZoom) {
- // insert here?
+ cameraChangeDispatcher.onCameraIdle();
quickZoom = false;
break;
}
@@ -310,7 +364,7 @@ final class MapGestureDetector {
@Override
public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
PointF tapPoint = new PointF(motionEvent.getX(), motionEvent.getY());
- boolean tapHandled = annotationManager.onTap(tapPoint, uiSettings.getPixelRatio());
+ boolean tapHandled = annotationManager.onTap(tapPoint);
if (!tapHandled) {
if (uiSettings.isDeselectMarkersOnTap()) {
@@ -318,10 +372,7 @@ final class MapGestureDetector {
annotationManager.deselectMarkers();
}
- // notify app of map click
- if (onMapClickListener != null) {
- onMapClickListener.onMapClick(projection.fromScreenLocation(tapPoint));
- }
+ notifyOnMapClickListeners(tapPoint);
}
MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent(
@@ -333,20 +384,20 @@ final class MapGestureDetector {
@Override
public void onLongPress(MotionEvent motionEvent) {
- if (onMapLongClickListener != null && !quickZoom) {
- onMapLongClickListener.onMapLongClick(
- projection.fromScreenLocation(new PointF(motionEvent.getX(), motionEvent.getY())));
+ PointF longClickPoint = new PointF(motionEvent.getX(), motionEvent.getY());
+
+ if (!quickZoom) {
+ notifyOnMapLongClickListeners(longClickPoint);
}
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- if ((!trackingSettings.isScrollGestureCurrentlyEnabled()) || scaleGestureOccurred) {
+ if ((!trackingSettings.isScrollGestureCurrentlyEnabled()) || recentScaleGestureOccurred) {
// don't allow a fling is scroll is disabled
// and ignore when a scale gesture has occurred
return false;
}
- cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
float screenDensity = uiSettings.getPixelRatio();
@@ -362,9 +413,11 @@ final class MapGestureDetector {
// cancel any animation
transform.cancelTransitions();
+ cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
+
// tilt results in a bigger translation, limiting input for #5281
double tilt = transform.getTilt();
- double tiltFactor = 1 + ((tilt != 0) ? (tilt / 10) : 0); /* 1 -> 7 */
+ double tiltFactor = 1.5 + ((tilt != 0) ? (tilt / 10) : 0);
double offsetX = velocityX / tiltFactor / screenDensity;
double offsetY = velocityY / tiltFactor / screenDensity;
@@ -374,9 +427,7 @@ final class MapGestureDetector {
// update transformation
transform.moveBy(offsetX, offsetY, animationTime);
- if (onFlingListener != null) {
- onFlingListener.onFling();
- }
+ notifyOnFlingListeners();
return true;
}
@@ -387,16 +438,19 @@ final class MapGestureDetector {
return false;
}
- if (dragStarted) {
+ if (tiltGestureOccurred) {
return false;
}
- if (!scrollInProgress) {
- scrollInProgress = true;
+ if (!scrollGestureOccurred) {
+ scrollGestureOccurred = true;
// Cancel any animation
- transform.cancelTransitions();
- cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
+ if (!scaleGestureOccurred) {
+ transform.cancelTransitions();
+ cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
+ }
+
MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent(
getLocationFromGesture(e1.getX(), e1.getY()),
MapboxEvent.GESTURE_PAN_START, transform));
@@ -408,20 +462,69 @@ final class MapGestureDetector {
// Scroll the map
transform.moveBy(-distanceX, -distanceY, 0 /*no duration*/);
- if (onScrollListener != null) {
- onScrollListener.onScroll();
- }
+ notifyOnScrollListeners();
return true;
}
}
+ void notifyOnMapClickListeners(PointF tapPoint) {
+ // deprecated API
+ if (onMapClickListener != null) {
+ onMapClickListener.onMapClick(projection.fromScreenLocation(tapPoint));
+ }
+
+ // new API
+ for (MapboxMap.OnMapClickListener listener : onMapClickListenerList) {
+ listener.onMapClick(projection.fromScreenLocation(tapPoint));
+ }
+ }
+
+ void notifyOnMapLongClickListeners(PointF longClickPoint) {
+ // deprecated API
+ if (onMapLongClickListener != null) {
+ onMapLongClickListener.onMapLongClick(projection.fromScreenLocation(longClickPoint));
+ }
+
+ // new API
+ for (MapboxMap.OnMapLongClickListener listener : onMapLongClickListenerList) {
+ listener.onMapLongClick(projection.fromScreenLocation(longClickPoint));
+ }
+ }
+
+ void notifyOnFlingListeners() {
+ // deprecated API
+ if (onFlingListener != null) {
+ onFlingListener.onFling();
+ }
+
+ // new API
+ for (MapboxMap.OnFlingListener listener : onFlingListenerList) {
+ listener.onFling();
+ }
+ }
+
+ void notifyOnScrollListeners() {
+ //deprecated API
+ if (onScrollListener != null) {
+ onScrollListener.onScroll();
+ }
+
+ // new API
+ for (MapboxMap.OnScrollListener listener : onScrollListenerList) {
+ listener.onScroll();
+ }
+ }
+
/**
* Responsible for handling two finger gestures and double-tap drag gestures.
*/
private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
- long beginTime = 0;
- float scaleFactor = 1.0f;
+ private static final int ANIMATION_TIME_MULTIPLIER = 77;
+ private static final double ZOOM_DISTANCE_DIVIDER = 5;
+
+ private float scaleFactor = 1.0f;
+ private PointF scalePointBegin;
// Called when two fingers first touch the screen
@Override
@@ -430,22 +533,15 @@ final class MapGestureDetector {
return false;
}
- scaleGestureOccurred = true;
- beginTime = detector.getEventTime();
+ recentScaleGestureOccurred = true;
+ scalePointBegin = new PointF(detector.getFocusX(), detector.getFocusY());
+ scaleBeginTime = detector.getEventTime();
MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent(
getLocationFromGesture(detector.getFocusX(), detector.getFocusY()),
MapboxEvent.GESTURE_PINCH_START, transform));
return true;
}
- // Called when fingers leave screen
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- beginTime = 0;
- scaleFactor = 1.0f;
- zoomStarted = false;
- }
-
// Called each time a finger moves
// Called for pinch zooms and quickzooms/quickscales
@Override
@@ -454,56 +550,126 @@ final class MapGestureDetector {
return super.onScale(detector);
}
- // If scale is large enough ignore a tap
- scaleFactor *= detector.getScaleFactor();
- if ((scaleFactor > 1.05f) || (scaleFactor < 0.95f)) {
- // notify camera change listener
- cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
- zoomStarted = true;
+ wasZoomingIn = (Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2)) >= 0;
+ if (tiltGestureOccurred) {
+ return false;
}
// Ignore short touches in case it is a tap
// Also ignore small scales
long time = detector.getEventTime();
- long interval = time - beginTime;
- if (!zoomStarted && (interval <= ViewConfiguration.getTapTimeout())) {
+ long interval = time - scaleBeginTime;
+ if (!scaleGestureOccurred && (interval <= ViewConfiguration.getTapTimeout())) {
return false;
}
- if (!zoomStarted) {
- return false;
+ // If scale is large enough ignore a tap
+ scaleFactor *= detector.getScaleFactor();
+ if ((scaleFactor > 1.1f) || (scaleFactor < 0.9f)) {
+ // notify camera change listener
+ cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
+ scaleGestureOccurred = true;
}
- if (dragStarted) {
+ if (!scaleGestureOccurred) {
return false;
}
// Gesture is a quickzoom if there aren't two fingers
+ if (!quickZoom && !twoTap) {
+ cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
+ }
quickZoom = !twoTap;
// make an assumption here; if the zoom center is specified by the gesture, it's NOT going
// to be in the center of the map. Therefore the zoom will translate the map center, so tracking
// should be disabled.
-
trackingSettings.resetTrackingModesIfRequired(!quickZoom, false, false);
// Scale the map
if (focalPoint != null) {
// arround user provided focal point
- transform.zoomBy(Math.log(detector.getScaleFactor()) / Math.log(2), focalPoint.x, focalPoint.y);
+ transform.zoomBy(Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2), focalPoint.x, focalPoint.y);
} else if (quickZoom) {
+ cameraChangeDispatcher.onCameraMove();
// clamp scale factors we feed to core #7514
- float scaleFactor = MathUtils.clamp(detector.getScaleFactor(),
+ float scaleFactor = detector.getScaleFactor();
+ // around center map
+ double zoomBy = Math.log(scaleFactor) / Math.log(Math.PI / 2);
+ boolean negative = zoomBy < 0;
+ zoomBy = MathUtils.clamp(Math.abs(zoomBy),
MapboxConstants.MINIMUM_SCALE_FACTOR_CLAMP,
MapboxConstants.MAXIMUM_SCALE_FACTOR_CLAMP);
- // around center map
- transform.zoomBy(Math.log(scaleFactor) / Math.log(2), uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
+ transform.zoomBy(negative ? -zoomBy : zoomBy, uiSettings.getWidth() / 2, uiSettings.getHeight() / 2);
+ recentScaleGestureOccurred = true;
} else {
// around gesture
- transform.zoomBy(Math.log(detector.getScaleFactor()) / Math.log(2), detector.getFocusX(), detector.getFocusY());
+ transform.zoomBy(Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2),
+ scalePointBegin.x, scalePointBegin.y);
}
-
return true;
}
+
+ // Called when fingers leave screen
+ @Override
+ public void onScaleEnd(final ScaleGestureDetector detector) {
+ if (velocityTracker == null) {
+ return;
+ }
+
+
+ if (rotateGestureOccurred || quickZoom) {
+ reset();
+ return;
+ }
+
+ double velocityXY = Math.abs(velocityTracker.getYVelocity()) + Math.abs(velocityTracker.getXVelocity());
+ if (velocityXY > MapboxConstants.VELOCITY_THRESHOLD_IGNORE_FLING / 2) {
+ scaleAnimating = true;
+ double zoomAddition = calculateScale(velocityXY);
+ double currentZoom = transform.getRawZoom();
+ long animationTime = (long) (Math.log(velocityXY) * ANIMATION_TIME_MULTIPLIER);
+ createScaleAnimator(currentZoom, zoomAddition, animationTime).start();
+ } else if (!scaleAnimating) {
+ reset();
+ }
+ }
+
+ private void reset() {
+ scaleAnimating = false;
+ scaleGestureOccurred = false;
+ scaleBeginTime = 0;
+ scaleFactor = 1.0f;
+ cameraChangeDispatcher.onCameraIdle();
+ }
+
+ private double calculateScale(double velocityXY) {
+ double zoomAddition = (float) (Math.log(velocityXY) / ZOOM_DISTANCE_DIVIDER);
+ if (!wasZoomingIn) {
+ zoomAddition = -zoomAddition;
+ }
+ return zoomAddition;
+ }
+
+ private Animator createScaleAnimator(double currentZoom, double zoomAddition, long animationTime) {
+ ValueAnimator animator = ValueAnimator.ofFloat((float) currentZoom, (float) (currentZoom + zoomAddition));
+ animator.setDuration(animationTime);
+ animator.setInterpolator(new FastOutSlowInInterpolator());
+ animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ transform.setZoom((Float) animation.getAnimatedValue(), scalePointBegin);
+ }
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ reset();
+ }
+ });
+ return animator;
+ }
}
/**
@@ -511,9 +677,13 @@ final class MapGestureDetector {
*/
private class RotateGestureListener extends RotateGestureDetector.SimpleOnRotateGestureListener {
- long beginTime = 0;
- float totalAngle = 0.0f;
- boolean started = false;
+ private static final float ROTATE_INVOKE_ANGLE = 15.30f;
+ private static final float ROTATE_LIMITATION_ANGLE = 3.35f;
+ private static final float ROTATE_LIMITATION_DURATION = ROTATE_LIMITATION_ANGLE * 1.85f;
+
+ private long beginTime = 0;
+ private boolean started = false;
+ private boolean animating = false;
// Called when two fingers first touch the screen
@Override
@@ -526,54 +696,42 @@ final class MapGestureDetector {
cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
beginTime = detector.getEventTime();
- MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent(
- getLocationFromGesture(detector.getFocusX(), detector.getFocusY()),
- MapboxEvent.GESTURE_ROTATION_START, transform));
return true;
}
- // Called when the fingers leave the screen
- @Override
- public void onRotateEnd(RotateGestureDetector detector) {
- // notify camera change listener
- beginTime = 0;
- totalAngle = 0.0f;
- started = false;
- }
-
// Called each time one of the two fingers moves
// Called for rotation
@Override
public boolean onRotate(RotateGestureDetector detector) {
- if (!trackingSettings.isRotateGestureCurrentlyEnabled() || dragStarted) {
+ if (!trackingSettings.isRotateGestureCurrentlyEnabled() || tiltGestureOccurred) {
return false;
}
// If rotate is large enough ignore a tap
// Also is zoom already started, don't rotate
- totalAngle += detector.getRotationDegreesDelta();
- if (!zoomStarted && ((totalAngle > 20.0f) || (totalAngle < -20.0f))) {
+ float angle = detector.getRotationDegreesDelta();
+ if (Math.abs(angle) >= ROTATE_INVOKE_ANGLE) {
+ MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent(
+ getLocationFromGesture(detector.getFocusX(), detector.getFocusY()),
+ MapboxEvent.GESTURE_ROTATION_START, transform));
started = true;
}
- // Ignore short touches in case it is a tap
- // Also ignore small rotate
- long time = detector.getEventTime();
- long interval = time - beginTime;
- if (!started && (interval <= ViewConfiguration.getTapTimeout())) {
+ if (!started) {
return false;
}
- if (!started) {
- return false;
+ wasClockwiseRotating = detector.getRotationDegreesDelta() > 0;
+ if (scaleBeginTime != 0) {
+ rotateGestureOccurred = true;
}
+
// rotation constitutes translation of anything except the center of
// rotation, so cancel both location and bearing tracking if required
trackingSettings.resetTrackingModesIfRequired(true, true, false);
- // Get rotate value
- double bearing = transform.getRawBearing();
- bearing += detector.getRotationDegreesDelta();
+ // Calculate map bearing value
+ double bearing = transform.getRawBearing() + angle;
// Rotate the map
if (focalPoint != null) {
@@ -585,6 +743,83 @@ final class MapGestureDetector {
}
return true;
}
+
+ // Called when the fingers leave the screen
+ @Override
+ public void onRotateEnd(RotateGestureDetector detector) {
+ long interval = detector.getEventTime() - beginTime;
+ if ((!started && (interval <= ViewConfiguration.getTapTimeout())) || scaleAnimating || interval > 500) {
+ reset();
+ return;
+ }
+
+ double angularVelocity = calculateVelocityVector(detector);
+ if (Math.abs(angularVelocity) > 0.001 && rotateGestureOccurred && !animating) {
+ animateRotateVelocity();
+ } else if (!animating) {
+ reset();
+ }
+ }
+
+ private void reset() {
+ beginTime = 0;
+ started = false;
+ animating = false;
+ rotateGestureOccurred = false;
+ }
+
+ private void animateRotateVelocity() {
+ animating = true;
+ double currentRotation = transform.getRawBearing();
+ double rotateAdditionDegrees = calculateVelocityInDegrees();
+ createAnimator(currentRotation, rotateAdditionDegrees).start();
+ }
+
+ private double calculateVelocityVector(RotateGestureDetector detector) {
+ return ((detector.getFocusX() * velocityTracker.getYVelocity())
+ + (detector.getFocusY() * velocityTracker.getXVelocity()))
+ / (Math.pow(detector.getFocusX(), 2) + Math.pow(detector.getFocusY(), 2));
+ }
+
+ private double calculateVelocityInDegrees() {
+ double angleRadians = Math.atan2(velocityTracker.getXVelocity(), velocityTracker.getYVelocity());
+ double angle = angleRadians / (Math.PI / 180);
+ if (angle <= 0) {
+ angle += 360;
+ }
+
+ // limit the angle
+ angle = angle / ROTATE_LIMITATION_ANGLE;
+
+ // correct direction
+ if (!wasClockwiseRotating) {
+ angle = -angle;
+ }
+
+ return angle;
+ }
+
+ private Animator createAnimator(double currentRotation, double rotateAdditionDegrees) {
+ ValueAnimator animator = ValueAnimator.ofFloat(
+ (float) currentRotation,
+ (float) (currentRotation + rotateAdditionDegrees)
+ );
+ animator.setDuration((long) (Math.abs(rotateAdditionDegrees) * ROTATE_LIMITATION_DURATION));
+ animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ transform.setBearing((Float) animation.getAnimatedValue());
+ }
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ reset();
+ }
+ });
+ return animator;
+ }
}
/**
@@ -592,9 +827,8 @@ final class MapGestureDetector {
*/
private class ShoveGestureListener implements ShoveGestureDetector.OnShoveGestureListener {
- long beginTime = 0;
- float totalDelta = 0.0f;
- boolean started = false;
+ private long beginTime = 0;
+ private float totalDelta = 0.0f;
@Override
public boolean onShoveBegin(ShoveGestureDetector detector) {
@@ -604,10 +838,6 @@ final class MapGestureDetector {
// notify camera change listener
cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
- beginTime = detector.getEventTime();
- MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent(
- getLocationFromGesture(detector.getFocusX(), detector.getFocusY()),
- MapboxEvent.GESTURE_PITCH_START, transform));
return true;
}
@@ -615,8 +845,7 @@ final class MapGestureDetector {
public void onShoveEnd(ShoveGestureDetector detector) {
beginTime = 0;
totalDelta = 0.0f;
- started = false;
- dragStarted = false;
+ tiltGestureOccurred = false;
}
@Override
@@ -625,22 +854,26 @@ final class MapGestureDetector {
return false;
}
- // If tilt is large enough ignore a tap
- // Also if zoom already started, don't tilt
- totalDelta += detector.getShovePixelsDelta();
- if (!zoomStarted && ((totalDelta > 10.0f) || (totalDelta < -10.0f))) {
- started = true;
- }
-
// Ignore short touches in case it is a tap
// Also ignore small tilt
long time = detector.getEventTime();
long interval = time - beginTime;
- if (!started && (interval <= ViewConfiguration.getTapTimeout())) {
+ if (!tiltGestureOccurred && (interval <= ViewConfiguration.getTapTimeout())) {
return false;
}
- if (!started) {
+ // If tilt is large enough ignore a tap
+ // Also if zoom already started, don't tilt
+ totalDelta += detector.getShovePixelsDelta();
+ if (!tiltGestureOccurred && ((totalDelta > 10.0f) || (totalDelta < -10.0f))) {
+ tiltGestureOccurred = true;
+ beginTime = detector.getEventTime();
+ MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent(
+ getLocationFromGesture(detector.getFocusX(), detector.getFocusY()),
+ MapboxEvent.GESTURE_PITCH_START, transform));
+ }
+
+ if (!tiltGestureOccurred) {
return false;
}
@@ -651,9 +884,6 @@ final class MapGestureDetector {
// Tilt the map
transform.setTilt(pitch);
-
- dragStarted = true;
-
return true;
}
}
@@ -673,4 +903,36 @@ final class MapGestureDetector {
void setOnScrollListener(MapboxMap.OnScrollListener onScrollListener) {
this.onScrollListener = onScrollListener;
}
+
+ void addOnMapClickListener(MapboxMap.OnMapClickListener onMapClickListener) {
+ onMapClickListenerList.add(onMapClickListener);
+ }
+
+ void removeOnMapClickListener(MapboxMap.OnMapClickListener onMapClickListener) {
+ onMapClickListenerList.remove(onMapClickListener);
+ }
+
+ void addOnMapLongClickListener(MapboxMap.OnMapLongClickListener onMapLongClickListener) {
+ onMapLongClickListenerList.add(onMapLongClickListener);
+ }
+
+ void removeOnMapLongClickListener(MapboxMap.OnMapLongClickListener onMapLongClickListener) {
+ onMapLongClickListenerList.remove(onMapLongClickListener);
+ }
+
+ void addOnFlingListener(MapboxMap.OnFlingListener onFlingListener) {
+ onFlingListenerList.add(onFlingListener);
+ }
+
+ void removeOnFlingListener(MapboxMap.OnFlingListener onFlingListener) {
+ onFlingListenerList.remove(onFlingListener);
+ }
+
+ void addOnScrollListener(MapboxMap.OnScrollListener onScrollListener) {
+ onScrollListenerList.add(onScrollListener);
+ }
+
+ void removeOnScrollListener(MapboxMap.OnScrollListener onScrollListener) {
+ onScrollListenerList.remove(onScrollListener);
+ }
}