Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
[android] - Add angular velocity effect on rotation gesture.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ramin Mirsharifi authored and tobrun committed Oct 17, 2017
1 parent 10d4d63 commit ffd0e59
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -419,7 +425,7 @@ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float d
return false;
}

if (tiltGestureOccurred || scaleGestureOccurred) {
if (tiltGestureOccurred) {
return false;
}

Expand Down Expand Up @@ -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
Expand All @@ -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()),
Expand All @@ -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;
}
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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;
}
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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;
}

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

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
Expand Down

0 comments on commit ffd0e59

Please sign in to comment.