diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java index 3d28c2295d1..d10b76bda29 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java @@ -56,7 +56,7 @@ public TwoFingerGestureDetector(Context context) { // lowering the edgeSlop allows to execute gesture faster // https://github.com/mapbox/mapbox-gl-native/issues/10102 - edgeSlop = config.getScaledEdgeSlop() / 3.0f; + edgeSlop = config.getScaledEdgeSlop(); // 3.0f; } @Override 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 95f34b9b70e..e2aed7ac47b 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,17 +1,21 @@ 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; import android.view.VelocityTracker; import android.view.ViewConfiguration; +import android.view.animation.LinearInterpolator; import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector; import com.almeros.android.multitouch.gesturedetectors.ShoveGestureDetector; @@ -59,11 +63,14 @@ 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; + private boolean detectorInvoke; MapGestureDetector(Context context, Transform transform, Projection projection, UiSettings uiSettings, TrackingSettings trackingSettings, AnnotationManager annotationManager, @@ -192,8 +199,7 @@ boolean onTouchEvent(MotionEvent event) { 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 +425,7 @@ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float d return false; } - if (tiltGestureOccurred || scaleGestureOccurred) { + if (tiltGestureOccurred) { return false; } @@ -456,6 +462,7 @@ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float d private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { private float scaleFactor = 1.0f; + private PointF scalePointBegin; // Called when two fingers first touch the screen @Override @@ -465,6 +472,7 @@ public boolean onScaleBegin(ScaleGestureDetector detector) { } recentScaleGestureOccurred = true; + scalePointBegin = new PointF(detector.getFocusX(), detector.getFocusY()); scaleBeginTime = detector.getEventTime(); MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent( getLocationFromGesture(detector.getFocusX(), detector.getFocusY()), @@ -474,38 +482,66 @@ public boolean onScaleBegin(ScaleGestureDetector detector) { // Called when fingers leave screen @Override - public void onScaleEnd(ScaleGestureDetector detector) { + public void onScaleEnd(final ScaleGestureDetector detector) { + if (rotateGestureOccurred) { + reset(); + return; + } + 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); + scaleAnimating = true; + long animationTime = (long) (Math.log(velocityXY) * 66); + double zoomAddition = (float) (Math.log(velocityXY) / 2.75); if (!wasZoomingIn) { zoomAddition = -zoomAddition; } - scaleGestureOccurred = true; - transform.zoom(zoomAddition, new PointF(detector.getFocusX(), detector.getFocusY()), animationTime); - handler.postDelayed(new Runnable() { + double currentZoom = transform.getRawZoom(); + ValueAnimator zoomAnimator = ValueAnimator.ofFloat((float) currentZoom, (float) (currentZoom + zoomAddition)); + zoomAnimator.setDuration(animationTime); + zoomAnimator.setInterpolator(new FastOutSlowInInterpolator()); + zoomAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override - public void run() { - scaleGestureOccurred = false; + public void onAnimationUpdate(ValueAnimator animation) { + transform.setZoom((Float) animation.getAnimatedValue(), scalePointBegin); } - }, animationTime); - } else { - scaleGestureOccurred = false; - scaleBeginTime = 0; - scaleFactor = 1.0f; - cameraChangeDispatcher.onCameraIdle(); + }); + zoomAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + reset(); + } + }); + zoomAnimator.start(); + } else if (!scaleAnimating) { + reset(); } } + private void reset() { + scaleAnimating = false; + scaleGestureOccurred = false; + detectorInvoke = false; + scaleBeginTime = 0; + scaleFactor = 1.0f; + cameraChangeDispatcher.onCameraIdle(); + } + // Called each time a finger moves // Called for pinch zooms and quickzooms/quickscales @Override public boolean onScale(ScaleGestureDetector detector) { + if (!detectorInvoke) { + detectorInvoke = true; + return false; + } if (!uiSettings.isZoomGesturesEnabled()) { return super.onScale(detector); } + wasZoomingIn = (Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2)) >= 0; if (tiltGestureOccurred) { return false; } @@ -514,13 +550,13 @@ public boolean onScale(ScaleGestureDetector detector) { // 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; @@ -542,7 +578,6 @@ public boolean onScale(ScaleGestureDetector detector) { 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); @@ -558,7 +593,7 @@ public boolean onScale(ScaleGestureDetector detector) { } else { // around gesture transform.zoomBy(Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2), - detector.getFocusX(), detector.getFocusY()); + scalePointBegin.x, scalePointBegin.y); } return true; } @@ -569,11 +604,11 @@ public boolean onScale(ScaleGestureDetector detector) { */ 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 = 13.5f; private long beginTime = 0; private boolean started = false; + private boolean animating = false; // Called when two fingers first touch the screen @Override @@ -592,9 +627,63 @@ public boolean onRotateBegin(RotateGestureDetector detector) { // Called when the fingers leave the screen @Override public void onRotateEnd(RotateGestureDetector detector) { - // notify camera change listener + long interval = detector.getEventTime() - beginTime; + if ((!started && (interval <= ViewConfiguration.getTapTimeout())) || scaleAnimating || interval > 500) { + reset(); + return; + } + + double angularVelocity = ((detector.getFocusX() * velocityTracker.getYVelocity()) + + (detector.getFocusY() * velocityTracker.getXVelocity())) + / (Math.pow(detector.getFocusX(), 2) + Math.pow(detector.getFocusY(), 2)); + if (Math.abs(angularVelocity) > 0.001 && rotateGestureOccurred && !animating) { + animating = true; + double rotateAdditionRadians = Math.atan2(velocityTracker.getXVelocity(), velocityTracker.getYVelocity()); + double rotateAdditionDegrees = rotateAdditionRadians / (Math.PI / 180); + if (rotateAdditionDegrees <= 0) { + rotateAdditionDegrees += 360; + } + + rotateAdditionDegrees = rotateAdditionDegrees / 2; + + long animationTime = (long) rotateAdditionDegrees * 3; + + if (!wasClockwiseRotating) { + rotateAdditionDegrees = -rotateAdditionDegrees; + } + + double currentRotation = transform.getRawBearing(); + ValueAnimator bearingAnimator = ValueAnimator.ofFloat( + (float) currentRotation, + (float) (currentRotation + rotateAdditionDegrees) + ); + + bearingAnimator.setDuration(animationTime); + bearingAnimator.setInterpolator(new LinearInterpolator()); + bearingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + transform.setBearing((Float) animation.getAnimatedValue()); + } + }); + bearingAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + reset(); + } + }); + bearingAnimator.start(); + } else if (!animating) { + reset(); + } + } + + private void reset() { beginTime = 0; started = false; + animating = false; + rotateGestureOccurred = false; } // Called each time one of the two fingers moves @@ -605,11 +694,7 @@ public boolean onRotate(RotateGestureDetector detector) { 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))) { + if (!detectorInvoke) { return false; } @@ -627,6 +712,9 @@ public boolean onRotate(RotateGestureDetector detector) { return false; } + wasClockwiseRotating = detector.getRotationDegreesDelta() > 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); @@ -644,13 +732,6 @@ public boolean onRotate(RotateGestureDetector detector) { } 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; - } } /** diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index 260933ffa15..c84e93558e8 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java @@ -414,6 +414,9 @@ public boolean onTrackballEvent(MotionEvent event) { @Override public boolean onGenericMotionEvent(MotionEvent event) { + if (mapGestureDetector == null) { + return super.onGenericMotionEvent(event); + } return mapGestureDetector.onGenericMotionEvent(event) || super.onGenericMotionEvent(event); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java index e18e7f91c6a..c5fd37f21e3 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java @@ -210,6 +210,10 @@ void setOnCameraChangeListener(@Nullable MapboxMap.OnCameraChangeListener listen return cameraPosition.zoom; } + double getRawZoom() { + return mapView.getZoom(); + } + void zoom(boolean zoomIn, @NonNull PointF focalPoint) { CameraPosition cameraPosition = invalidateCameraPosition(); if (cameraPosition != null) { @@ -221,7 +225,7 @@ void zoom(boolean zoomIn, @NonNull PointF focalPoint) { } } - void zoom(double zoomAddition, @NonNull PointF focalPoint,long duration) { + void zoom(double zoomAddition, @NonNull PointF focalPoint, long duration) { CameraPosition cameraPosition = invalidateCameraPosition(); if (cameraPosition != null) { int newZoom = (int) Math.round(cameraPosition.zoom + zoomAddition);