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.java233
1 files changed, 168 insertions, 65 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 95f34b9b70..4120e164a4 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,12 +1,15 @@
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.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.ScaleGestureDetectorCompat;
+import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
@@ -59,11 +62,13 @@ final class MapGestureDetector {
private boolean scaleGestureOccurred;
private boolean recentScaleGestureOccurred;
+ private boolean scaleAnimating;
private long scaleBeginTime;
- private VelocityTracker velocityTracker = null;
- private boolean wasZoomingIn = false;
- private final Handler handler = new Handler();
+ 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,
@@ -192,8 +197,7 @@ final class MapGestureDetector {
boolean isTap = tapInterval <= ViewConfiguration.getTapTimeout();
boolean inProgress = rotateGestureDetector.isInProgress()
|| scaleGestureDetector.isInProgress()
- || shoveGestureDetector.isInProgress()
- || scaleGestureOccurred;
+ || shoveGestureDetector.isInProgress();
if (twoTap && isTap && !inProgress) {
if (focalPoint != null) {
@@ -419,7 +423,7 @@ final class MapGestureDetector {
return false;
}
- if (tiltGestureOccurred || scaleGestureOccurred) {
+ if (tiltGestureOccurred) {
return false;
}
@@ -455,7 +459,11 @@ final class MapGestureDetector {
*/
private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
+ private static final int ANIMATION_TIME_MULTIPLIER = 77;
+ private static final double ZOOM_DISTANCE_DIVIDER = 5;
+
private float scaleFactor = 1.0f;
+ private PointF scalePointBegin;
// Called when two fingers first touch the screen
@Override
@@ -465,6 +473,7 @@ final class MapGestureDetector {
}
recentScaleGestureOccurred = true;
+ scalePointBegin = new PointF(detector.getFocusX(), detector.getFocusY());
scaleBeginTime = detector.getEventTime();
MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent(
getLocationFromGesture(detector.getFocusX(), detector.getFocusY()),
@@ -472,32 +481,6 @@ final class MapGestureDetector {
return true;
}
- // Called when fingers leave screen
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- double velocityXY = Math.abs(velocityTracker.getYVelocity()) + Math.abs(velocityTracker.getXVelocity());
- if (velocityXY > MapboxConstants.VELOCITY_THRESHOLD_IGNORE_FLING / 2) {
- long animationTime = (long)(Math.log(velocityXY) * 66);
- double zoomAddition = (float) (Math.log(velocityXY) / 7.7);
- if (!wasZoomingIn) {
- zoomAddition = -zoomAddition;
- }
- scaleGestureOccurred = true;
- transform.zoom(zoomAddition, new PointF(detector.getFocusX(), detector.getFocusY()), animationTime);
- handler.postDelayed(new Runnable() {
- @Override
- public void run() {
- scaleGestureOccurred = false;
- }
- }, animationTime);
- } else {
- scaleGestureOccurred = false;
- scaleBeginTime = 0;
- scaleFactor = 1.0f;
- cameraChangeDispatcher.onCameraIdle();
- }
- }
-
// Called each time a finger moves
// Called for pinch zooms and quickzooms/quickscales
@Override
@@ -506,6 +489,7 @@ final class MapGestureDetector {
return super.onScale(detector);
}
+ wasZoomingIn = (Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2)) >= 0;
if (tiltGestureOccurred) {
return false;
}
@@ -514,13 +498,13 @@ final class MapGestureDetector {
// Also ignore small scales
long time = detector.getEventTime();
long interval = time - scaleBeginTime;
- if (!scaleGestureOccurred && (interval <= ViewConfiguration.getTapTimeout() / 3)) {
+ if (!scaleGestureOccurred && (interval <= ViewConfiguration.getTapTimeout())) {
return false;
}
// If scale is large enough ignore a tap
scaleFactor *= detector.getScaleFactor();
- if ((scaleFactor > 1.05f) || (scaleFactor < 0.95f)) {
+ if ((scaleFactor > 1.1f) || (scaleFactor < 0.9f)) {
// notify camera change listener
cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE);
scaleGestureOccurred = true;
@@ -539,29 +523,87 @@ final class MapGestureDetector {
// 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
- wasZoomingIn = (Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2)) >= 0;
if (focalPoint != null) {
// arround user provided focal point
transform.zoomBy(Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2), focalPoint.x, focalPoint.y);
} else if (quickZoom) {
cameraChangeDispatcher.onCameraMove();
// clamp scale factors we feed to core #7514
- float scaleFactor = 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(Math.PI / 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(Math.PI / 2),
- detector.getFocusX(), detector.getFocusY());
+ scalePointBegin.x, scalePointBegin.y);
}
return true;
}
+
+ // Called when fingers leave screen
+ @Override
+ public void onScaleEnd(final ScaleGestureDetector detector) {
+ 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;
+ }
}
/**
@@ -569,11 +611,13 @@ final class MapGestureDetector {
*/
private class RotateGestureListener extends RotateGestureDetector.SimpleOnRotateGestureListener {
- private static final long ROTATE_INVOKE_WAIT_TIME = 750;
- private static final float ROTATE_INVOKE_ANGLE = 17.5f;
+ 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
@@ -589,14 +633,6 @@ final class MapGestureDetector {
return true;
}
- // Called when the fingers leave the screen
- @Override
- public void onRotateEnd(RotateGestureDetector detector) {
- // notify camera change listener
- beginTime = 0;
- started = false;
- }
-
// Called each time one of the two fingers moves
// Called for rotation
@Override
@@ -605,14 +641,6 @@ final class MapGestureDetector {
return false;
}
- // 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() || isScaleGestureActive(time))) {
- return false;
- }
-
// If rotate is large enough ignore a tap
// Also is zoom already started, don't rotate
float angle = detector.getRotationDegreesDelta();
@@ -627,6 +655,11 @@ final class MapGestureDetector {
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);
@@ -645,11 +678,81 @@ final class MapGestureDetector {
return true;
}
- private boolean isScaleGestureActive(long time) {
- long scaleExecutionTime = time - scaleBeginTime;
- boolean scaleGestureStarted = scaleBeginTime != 0;
- boolean scaleOffsetTimeValid = scaleExecutionTime > ROTATE_INVOKE_WAIT_TIME;
- return (scaleGestureStarted && scaleOffsetTimeValid) || scaleGestureOccurred;
+ // 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;
}
}