diff options
author | Łukasz Paczos <lukasz.paczos@mapbox.com> | 2018-02-27 18:35:05 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-02-27 18:35:05 +0100 |
commit | 848d7dff4e682c3d2485366f95649a5de4b1bd75 (patch) | |
tree | cea25172d92d43c222407ea7b6729f00a548af79 /platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java | |
parent | 43a7db0b0bfd7d626ffed9adff3446fbbe111c15 (diff) | |
download | qtlocation-mapboxgl-848d7dff4e682c3d2485366f95649a5de4b1bd75.tar.gz |
[android ] - new gestures library (cp #11221) (#11332)
* [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
(cherry picked from commit 7905bd6)
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.java | 1096 |
1 files changed, 558 insertions, 538 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 8047e19809..5f5a10d0d0 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 @@ -5,26 +5,31 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.PointF; +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 com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector; -import com.almeros.android.multitouch.gesturedetectors.ShoveGestureDetector; -import com.almeros.android.multitouch.gesturedetectors.TwoFingerGestureDetector; +import android.view.animation.DecelerateInterpolator; + +import com.mapbox.android.gestures.AndroidGesturesManager; +import com.mapbox.android.gestures.MoveGestureDetector; +import com.mapbox.android.gestures.MultiFingerTapGestureDetector; +import com.mapbox.android.gestures.RotateGestureDetector; +import com.mapbox.android.gestures.ShoveGestureDetector; +import com.mapbox.android.gestures.StandardGestureDetector; +import com.mapbox.android.gestures.StandardScaleGestureDetector; import com.mapbox.android.telemetry.Event; import com.mapbox.android.telemetry.MapEventFactory; import com.mapbox.android.telemetry.MapState; +import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.utils.MathUtils; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener.REASON_API_ANIMATION; @@ -32,24 +37,15 @@ import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener.RE /** * Manages gestures events on a MapView. - * <p> - * Relies on gesture detection code in almeros.android.multitouch.gesturedetectors. - * </p> */ final class MapGestureDetector { private final Transform transform; private final Projection projection; private final UiSettings uiSettings; - private final TrackingSettings trackingSettings; private final AnnotationManager annotationManager; private final CameraChangeDispatcher cameraChangeDispatcher; - private GestureDetectorCompat gestureDetector; - private ScaleGestureDetector scaleGestureDetector; - private RotateGestureDetector rotateGestureDetector; - private ShoveGestureDetector shoveGestureDetector; - // deprecated map touch API private MapboxMap.OnMapClickListener onMapClickListener; private MapboxMap.OnMapLongClickListener onMapLongClickListener; @@ -69,43 +65,73 @@ final class MapGestureDetector { private final CopyOnWriteArrayList<MapboxMap.OnScrollListener> onScrollListenerList = new CopyOnWriteArrayList<>(); - private PointF focalPoint; + private final CopyOnWriteArrayList<MapboxMap.OnMoveListener> onMoveListenerList + = new CopyOnWriteArrayList<>(); - private boolean twoTap; - private boolean quickZoom; - private boolean tiltGestureOccurred; - private boolean scrollGestureOccurred; + private final CopyOnWriteArrayList<MapboxMap.OnRotateListener> onRotateListenerList + = new CopyOnWriteArrayList<>(); + + private final CopyOnWriteArrayList<MapboxMap.OnScaleListener> onScaleListenerList + = new CopyOnWriteArrayList<>(); + + private final CopyOnWriteArrayList<MapboxMap.OnShoveListener> onShoveListenerList + = new CopyOnWriteArrayList<>(); + + /** + * User-set focal point. + */ + private PointF focalPoint; - private boolean scaleGestureOccurred; - private boolean recentScaleGestureOccurred; - private long scaleBeginTime; + private AndroidGesturesManager gesturesManager; + private boolean executeDoubleTap; - private boolean scaleAnimating; - private boolean rotateAnimating; + private Animator scaleAnimator; + private Animator rotateAnimator; + private final List<Animator> scheduledAnimators = new ArrayList<>(); - private VelocityTracker velocityTracker; - private boolean wasZoomingIn; - private boolean wasClockwiseRotating; - private boolean rotateGestureOccurred; + /** + * Cancels scheduled velocity animations if user doesn't lift fingers within + * {@link MapboxConstants#SCHEDULED_ANIMATION_TIMEOUT} + */ + private Handler animationsTimeoutHandler = new Handler(); MapGestureDetector(Context context, Transform transform, Projection projection, UiSettings uiSettings, - TrackingSettings trackingSettings, AnnotationManager annotationManager, - CameraChangeDispatcher cameraChangeDispatcher) { + AnnotationManager annotationManager, CameraChangeDispatcher cameraChangeDispatcher) { this.annotationManager = annotationManager; this.transform = transform; this.projection = projection; this.uiSettings = uiSettings; - this.trackingSettings = trackingSettings; this.cameraChangeDispatcher = cameraChangeDispatcher; - // Touch gesture detectors + // Checking for context != null for testing purposes 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()); + gesturesManager = new AndroidGesturesManager(context); + + Set<Integer> shoveScaleSet = new HashSet<>(); + shoveScaleSet.add(AndroidGesturesManager.GESTURE_TYPE_SHOVE); + shoveScaleSet.add(AndroidGesturesManager.GESTURE_TYPE_SCALE); + + Set<Integer> shoveRotateSet = new HashSet<>(); + shoveRotateSet.add(AndroidGesturesManager.GESTURE_TYPE_SHOVE); + shoveRotateSet.add(AndroidGesturesManager.GESTURE_TYPE_ROTATE); + + Set<Integer> ScaleLongPressSet = new HashSet<>(); + ScaleLongPressSet.add(AndroidGesturesManager.GESTURE_TYPE_SCALE); + ScaleLongPressSet.add(AndroidGesturesManager.GESTURE_TYPE_LONG_PRESS); + + gesturesManager.setMutuallyExclusiveGestures(shoveScaleSet, shoveRotateSet, ScaleLongPressSet); + + gesturesManager.setStandardGestureListener(new StandardGestureListener()); + gesturesManager.setMoveGestureListener(new MoveGestureListener()); + gesturesManager.setStandardScaleGestureListener(new ScaleGestureListener( + context.getResources().getDimension(R.dimen.mapbox_minimum_scale_velocity) + )); + gesturesManager.setRotateGestureListener(new RotateGestureListener( + context.getResources().getDimension(R.dimen.mapbox_minimum_scale_span_when_rotating), + context.getResources().getDimension(R.dimen.mapbox_minimum_angular_velocity) + )); + gesturesManager.setShoveGestureListener(new ShoveGestureListener()); + gesturesManager.setMultiFingerTapGestureListener(new TapGestureListener()); } } @@ -132,8 +158,9 @@ final class MapGestureDetector { /** * Get the current active gesture focal point. * <p> - * This could be either the user provided focal point in {@link UiSettings#setFocalPoint(PointF)} or the focal point - * defined as a result of {@link TrackingSettings#setMyLocationEnabled(boolean)}. + * This could be either the user provided focal point in + * {@link UiSettings#setFocalPoint(PointF)}or <code>null</code>. + * If it's <code>null</code>, gestures will use focal pointed returned by the detector. * </p> * * @return the current active gesture focal point. @@ -149,118 +176,79 @@ final class MapGestureDetector { * Forwards event to the related gesture detectors. * </p> * - * @param event the MotionEvent + * @param motionEvent the MotionEvent * @return True if touch event is handled */ - boolean onTouchEvent(MotionEvent event) { - // framework can return null motion events in edge cases #9432 - if (event == null) { + boolean onTouchEvent(MotionEvent motionEvent) { + // Framework can return null motion events in edge cases #9432 + if (motionEvent == null) { return false; } // Check and ignore non touch or left clicks - if ((event.getButtonState() != 0) && (event.getButtonState() != MotionEvent.BUTTON_PRIMARY)) { + if ((motionEvent.getButtonState() != 0) && (motionEvent.getButtonState() != MotionEvent.BUTTON_PRIMARY)) { return false; } - // Check two finger gestures first - scaleGestureDetector.onTouchEvent(event); - rotateGestureDetector.onTouchEvent(event); - shoveGestureDetector.onTouchEvent(event); + boolean result = gesturesManager.onTouchEvent(motionEvent); - // Handle two finger tap - switch (event.getActionMasked()) { + switch (motionEvent.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 - recentScaleGestureOccurred = false; + cancelAnimators(); transform.setGestureInProgress(true); break; + case MotionEvent.ACTION_UP: + transform.setGestureInProgress(false); - case MotionEvent.ACTION_POINTER_DOWN: - // Second pointer down - twoTap = event.getPointerCount() == 2 - && uiSettings.isZoomGesturesEnabled(); - if (twoTap) { - // Confirmed 2nd Finger Down - if (isZoomValid(transform)) { - MapEventFactory mapEventFactory = new MapEventFactory(); - LatLng latLng = projection.fromScreenLocation(new PointF(event.getX(), event.getY())); - MapState twoFingerTap = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom()); - twoFingerTap.setGesture(Events.TWO_FINGER_TAP); - Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, twoFingerTap)); - } + // Start all awaiting velocity animations + animationsTimeoutHandler.removeCallbacksAndMessages(null); + for (Animator animator : scheduledAnimators) { + animator.start(); } + scheduledAnimators.clear(); break; - case MotionEvent.ACTION_POINTER_UP: - // Second pointer up + case MotionEvent.ACTION_CANCEL: + scheduledAnimators.clear(); + transform.setGestureInProgress(false); break; + } - case MotionEvent.ACTION_UP: - // First pointer up - long tapInterval = event.getEventTime() - event.getDownTime(); - boolean isTap = tapInterval <= ViewConfiguration.getTapTimeout(); - boolean inProgress = rotateGestureDetector.isInProgress() - || scaleGestureDetector.isInProgress() - || shoveGestureDetector.isInProgress(); - - if (twoTap && isTap && !inProgress) { - if (focalPoint != null) { - transform.zoom(false, focalPoint); - } else { - PointF focalPoint = TwoFingerGestureDetector.determineFocalPoint(event); - transform.zoom(false, focalPoint); - } - twoTap = false; - return true; - } - - // Scroll / Pan Has Stopped - if (scrollGestureOccurred) { - if (isZoomValid(transform)) { - MapEventFactory mapEventFactory = new MapEventFactory(); - LatLng latLng = projection.fromScreenLocation(new PointF(event.getX(), event.getY())); - MapState dragend = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom()); - Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_DRAGEND, dragend)); - } - scrollGestureOccurred = false; + return result; + } - if (!scaleAnimating && !rotateAnimating) { - cameraChangeDispatcher.onCameraIdle(); - } - } + void cancelAnimators() { + if (scaleAnimator != null) { + scaleAnimator.cancel(); + } + if (rotateAnimator != null) { + rotateAnimator.cancel(); + } - twoTap = false; - transform.setGestureInProgress(false); - if (velocityTracker != null) { - velocityTracker.recycle(); - } - velocityTracker = null; - break; + animationsTimeoutHandler.removeCallbacksAndMessages(null); + scheduledAnimators.clear(); + } - 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; + /** + * Posted on main thread with {@link #animationsTimeoutHandler}. Cancels all scheduled animators if needed. + */ + private Runnable cancelAnimatorsRunnable = new Runnable() { + @Override + public void run() { + cancelAnimators(); } + }; - return gestureDetector.onTouchEvent(event); + /** + * Schedules a velocity animator to be executed when user lift fingers, + * unless canceled by the {@link #cancelAnimatorsRunnable}. + * + * @param animator animator ot be scheduled + */ + private void scheduleAnimator(Animator animator) { + scheduledAnimators.add(animator); + animationsTimeoutHandler.removeCallbacksAndMessages(null); + animationsTimeoutHandler.postDelayed(cancelAnimatorsRunnable, MapboxConstants.SCHEDULED_ANIMATION_TIMEOUT); } /** @@ -269,7 +257,7 @@ final class MapGestureDetector { * Examples of such events are mouse scroll events, mouse moves, joystick & trackpad. * </p> * - * @param event The MotionEvent occured + * @param event The MotionEvent occurred * @return True is the event is handled */ boolean onGenericMotionEvent(MotionEvent event) { @@ -291,7 +279,7 @@ final class MapGestureDetector { float scrollDist = event.getAxisValue(MotionEvent.AXIS_VSCROLL); // Scale the map by the appropriate power of two factor - transform.zoomBy(scrollDist, event.getX(), event.getY()); + transform.zoomBy(scrollDist, new PointF(event.getX(), event.getY())); return true; @@ -305,61 +293,14 @@ final class MapGestureDetector { return false; } - /** - * Responsible for handling one finger gestures. - */ - private class GestureListener extends android.view.GestureDetector.SimpleOnGestureListener { - + private final class StandardGestureListener extends StandardGestureDetector.SimpleStandardOnGestureListener { @Override - public boolean onDown(MotionEvent event) { - return true; - } - - @Override - public boolean onDoubleTapEvent(MotionEvent e) { - if (!uiSettings.isZoomGesturesEnabled() || !uiSettings.isDoubleTapGesturesEnabled()) { - return false; - } - - switch (e.getAction()) { - case MotionEvent.ACTION_DOWN: - break; - case MotionEvent.ACTION_MOVE: - break; - case MotionEvent.ACTION_UP: - if (quickZoom) { - cameraChangeDispatcher.onCameraIdle(); - quickZoom = false; - break; - } - - // notify camera change listener - cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE); - - // Single finger double tap - if (focalPoint != null) { - // User provided focal point - transform.zoom(true, focalPoint); - } else { - // Zoom in on gesture - transform.zoom(true, new PointF(e.getX(), e.getY())); - } - if (isZoomValid(transform)) { - MapEventFactory mapEventFactory = new MapEventFactory(); - LatLng latLng = projection.fromScreenLocation(new PointF(e.getX(), e.getY())); - MapState doubleTap = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom()); - doubleTap.setGesture(Events.DOUBLE_TAP); - Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, doubleTap)); - } - break; - } - + public boolean onDown(MotionEvent motionEvent) { return true; } @Override public boolean onSingleTapUp(MotionEvent motionEvent) { - // Cancel any animation transform.cancelTransitions(); return true; } @@ -378,31 +319,51 @@ final class MapGestureDetector { notifyOnMapClickListeners(tapPoint); } - if (isZoomValid(transform)) { - MapEventFactory mapEventFactory = new MapEventFactory(); - LatLng latLng = projection.fromScreenLocation(new PointF(motionEvent.getX(), motionEvent.getY())); - MapState singleTap = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom()); - singleTap.setGesture(Events.SINGLE_TAP); - Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, singleTap)); - } + sendTelemetryEvent(Events.SINGLE_TAP, new PointF(motionEvent.getX(), motionEvent.getY())); return true; } @Override - public void onLongPress(MotionEvent motionEvent) { - PointF longClickPoint = new PointF(motionEvent.getX(), motionEvent.getY()); + public boolean onDoubleTapEvent(MotionEvent motionEvent) { + int action = motionEvent.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN) { + executeDoubleTap = true; + } + if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) { + if (!uiSettings.isZoomGesturesEnabled() || !uiSettings.isDoubleTapGesturesEnabled() || !executeDoubleTap) { + return false; + } + + transform.cancelTransitions(); + cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE); - if (!quickZoom) { - notifyOnMapLongClickListeners(longClickPoint); + // Single finger double tap + if (focalPoint != null) { + // User provided focal point + transform.zoomIn(focalPoint); + } else { + // Zoom in on gesture + transform.zoomIn(new PointF(motionEvent.getX(), motionEvent.getY())); + } + + sendTelemetryEvent(Events.DOUBLE_TAP, new PointF(motionEvent.getX(), motionEvent.getY())); + + return true; } + return super.onDoubleTapEvent(motionEvent); + } + + @Override + public void onLongPress(MotionEvent motionEvent) { + PointF longClickPoint = new PointF(motionEvent.getX(), motionEvent.getY()); + notifyOnMapLongClickListeners(longClickPoint); } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - if ((!trackingSettings.isScrollGestureCurrentlyEnabled()) || recentScaleGestureOccurred) { + if ((!uiSettings.isScrollGesturesEnabled())) { // don't allow a fling is scroll is disabled - // and ignore when a scale gesture has occurred return false; } @@ -415,11 +376,7 @@ final class MapGestureDetector { return false; } - trackingSettings.resetTrackingModesIfRequired(true, false, false); - - // cancel any animation transform.cancelTransitions(); - cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE); // tilt results in a bigger translation, limiting input for #5281 @@ -435,230 +392,149 @@ final class MapGestureDetector { transform.moveBy(offsetX, offsetY, animationTime); notifyOnFlingListeners(); + return true; } + } - // Called for drags + private final class MoveGestureListener extends MoveGestureDetector.SimpleOnMoveGestureListener { @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if (!trackingSettings.isScrollGestureCurrentlyEnabled()) { + public boolean onMoveBegin(MoveGestureDetector detector) { + if (!uiSettings.isScrollGesturesEnabled()) { return false; } - if (tiltGestureOccurred) { - return false; - } + transform.cancelTransitions(); + cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE); - if (!scrollGestureOccurred) { - scrollGestureOccurred = true; + sendTelemetryEvent(Events.PAN, detector.getFocalPoint()); - // Cancel any animation - if (!scaleGestureOccurred) { - transform.cancelTransitions(); - cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE); - } + notifyOnMoveBeginListeners(detector); - if (isZoomValid(transform)) { - MapEventFactory mapEventFactory = new MapEventFactory(); - LatLng latLng = projection.fromScreenLocation(new PointF(e1.getX(), e1.getY())); - MapState pan = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom()); - pan.setGesture(Events.PAN); - Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, pan)); - } - } + return true; + } - // reset tracking if needed - trackingSettings.resetTrackingModesIfRequired(true, false, false); + @Override + public boolean onMove(MoveGestureDetector detector, float distanceX, float distanceY) { + // dispatching start even once more if another detector ended, and this one didn't + cameraChangeDispatcher.onCameraMoveStarted(CameraChangeDispatcher.REASON_API_GESTURE); // Scroll the map transform.moveBy(-distanceX, -distanceY, 0 /*no duration*/); notifyOnScrollListeners(); + notifyOnMoveListeners(detector); 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)); + @Override + public void onMoveEnd(MoveGestureDetector detector, float velocityX, float velocityY) { + cameraChangeDispatcher.onCameraIdle(); + notifyOnMoveEndListeners(detector); } } - void notifyOnMapLongClickListeners(PointF longClickPoint) { - // deprecated API - if (onMapLongClickListener != null) { - onMapLongClickListener.onMapLongClick(projection.fromScreenLocation(longClickPoint)); - } + private final class ScaleGestureListener extends StandardScaleGestureDetector.SimpleStandardOnScaleGestureListener { - // new API - for (MapboxMap.OnMapLongClickListener listener : onMapLongClickListenerList) { - listener.onMapLongClick(projection.fromScreenLocation(longClickPoint)); - } - } + private final float minimumVelocity; - void notifyOnFlingListeners() { - // deprecated API - if (onFlingListener != null) { - onFlingListener.onFling(); - } + private PointF scaleFocalPoint; + private boolean quickZoom; - // new API - for (MapboxMap.OnFlingListener listener : onFlingListenerList) { - listener.onFling(); + ScaleGestureListener(float minimumVelocity) { + this.minimumVelocity = minimumVelocity; } - } - 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 { - - 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 - public boolean onScaleBegin(ScaleGestureDetector detector) { + public boolean onScaleBegin(StandardScaleGestureDetector detector) { if (!uiSettings.isZoomGesturesEnabled()) { return false; } - recentScaleGestureOccurred = true; - scalePointBegin = new PointF(detector.getFocusX(), detector.getFocusY()); - scaleBeginTime = detector.getEventTime(); - if (isZoomValid(transform)) { - MapEventFactory mapEventFactory = new MapEventFactory(); - LatLng latLng = projection.fromScreenLocation(new PointF(detector.getFocusX(), detector.getFocusY())); - MapState pinch = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom()); - pinch.setGesture(Events.PINCH); - Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, pinch)); + transform.cancelTransitions(); + cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE); + + quickZoom = detector.getPointersCount() == 1; + if (quickZoom) { + // when quickzoom, dismiss double tap and disable move gesture + executeDoubleTap = false; + gesturesManager.getMoveGestureDetector().setEnabled(false); } + + // increase rotate angle threshold when scale is detected first + gesturesManager.getRotateGestureDetector().setAngleThreshold( + gesturesManager.getRotateGestureDetector().getDefaultAngleThreshold() + + MapboxConstants.ROTATION_THRESHOLD_INCREASE_WHEN_SCALING + ); + + // setting focalPoint in #onScaleBegin() as well, because #onScale() might not get called before #onScaleEnd() + setScaleFocalPoint(detector); + + sendTelemetryEvent(Events.PINCH, scaleFocalPoint); + + notifyOnScaleBeginListeners(detector); + return true; } - // Called each time a finger moves - // Called for pinch zooms and quickzooms/quickscales @Override - public boolean onScale(ScaleGestureDetector detector) { - if (!uiSettings.isZoomGesturesEnabled()) { - return super.onScale(detector); - } + public boolean onScale(StandardScaleGestureDetector detector) { + // dispatching start even once more if another detector ended, and this one didn't + cameraChangeDispatcher.onCameraMoveStarted(CameraChangeDispatcher.REASON_API_GESTURE); - 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 - scaleBeginTime; - if (!scaleGestureOccurred && (interval <= ViewConfiguration.getTapTimeout())) { - 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; - } + setScaleFocalPoint(detector); - if (!scaleGestureOccurred) { - return false; - } + float scaleFactor = detector.getScaleFactor(); + double zoomBy = getNewZoom(scaleFactor, quickZoom); + transform.zoomBy(zoomBy, scaleFocalPoint); - // Gesture is a quickzoom if there aren't two fingers - if (!quickZoom && !twoTap) { - cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE); - } - quickZoom = !twoTap; + notifyOnScaleListeners(detector); - // 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(Math.PI / 2), focalPoint.x, focalPoint.y); - } else if (quickZoom) { - cameraChangeDispatcher.onCameraMove(); - // clamp scale factors we feed to core #7514 - 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); - 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), - scalePointBegin.x, scalePointBegin.y); - } return true; } - // Called when fingers leave screen @Override - public void onScaleEnd(final ScaleGestureDetector detector) { - if (velocityTracker == null) { - return; - } + public void onScaleEnd(StandardScaleGestureDetector detector, float velocityX, float velocityY) { + cameraChangeDispatcher.onCameraIdle(); - if (rotateGestureOccurred || quickZoom) { - reset(); - return; + if (quickZoom) { + //if quickzoom, re-enabling move gesture detector + gesturesManager.getMoveGestureDetector().setEnabled(true); } - double velocityXY = Math.abs(velocityTracker.getYVelocity()) + Math.abs(velocityTracker.getXVelocity()); - if (velocityXY > MapboxConstants.VELOCITY_THRESHOLD_IGNORE_FLING / 2) { - scaleAnimating = true; - double zoomAddition = calculateScale(velocityXY); + // resetting default angle threshold + gesturesManager.getRotateGestureDetector().setAngleThreshold( + gesturesManager.getRotateGestureDetector().getDefaultAngleThreshold() + ); + + float velocityXY = Math.abs(velocityX) + Math.abs(velocityY); + if (velocityXY > minimumVelocity) { + double zoomAddition = calculateScale(velocityXY, detector.isScalingOut()); double currentZoom = transform.getRawZoom(); - long animationTime = (long) (Math.log(velocityXY) * ANIMATION_TIME_MULTIPLIER); - createScaleAnimator(currentZoom, zoomAddition, animationTime).start(); - } else if (!scaleAnimating) { - reset(); + long animationTime = (long) (Math.abs(zoomAddition) * 1000 / 4); + scaleAnimator = createScaleAnimator(currentZoom, zoomAddition, animationTime); + scheduleAnimator(scaleAnimator); } + + notifyOnScaleEndListeners(detector); } - private void reset() { - scaleAnimating = false; - scaleGestureOccurred = false; - scaleBeginTime = 0; - scaleFactor = 1.0f; - cameraChangeDispatcher.onCameraIdle(); + private void setScaleFocalPoint(StandardScaleGestureDetector detector) { + if (focalPoint != null) { + // around user provided focal point + scaleFocalPoint = focalPoint; + } else if (quickZoom) { + // around center + scaleFocalPoint = new PointF(uiSettings.getWidth() / 2, uiSettings.getHeight() / 2); + } else { + // around gesture + scaleFocalPoint = detector.getFocalPoint(); + } } - private double calculateScale(double velocityXY) { - double zoomAddition = (float) (Math.log(velocityXY) / ZOOM_DISTANCE_DIVIDER); - if (!wasZoomingIn) { + private double calculateScale(double velocityXY, boolean isScalingOut) { + double zoomAddition = (float) Math.log(velocityXY / 1000 + 1); + if (isScalingOut) { zoomAddition = -zoomAddition; } return zoomAddition; @@ -667,272 +543,387 @@ final class MapGestureDetector { 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.setInterpolator(new DecelerateInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { - transform.setZoom((Float) animation.getAnimatedValue(), scalePointBegin, 0, true); + transform.setZoom((Float) animation.getAnimatedValue(), scaleFocalPoint, 0); } }); + animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { + transform.cancelTransitions(); cameraChangeDispatcher.onCameraMoveStarted(REASON_API_ANIMATION); } @Override public void onAnimationCancel(Animator animation) { - reset(); + transform.cancelTransitions(); } @Override public void onAnimationEnd(Animator animation) { - reset(); + cameraChangeDispatcher.onCameraIdle(); } }); return animator; } - } - /** - * Responsible for handling rotation gestures. - */ - private class RotateGestureListener extends RotateGestureDetector.SimpleOnRotateGestureListener { + private double getNewZoom(float scaleFactor, boolean quickZoom) { + double zoomBy = Math.log(scaleFactor) / Math.log(Math.PI / 2); + if (quickZoom) { + // clamp scale factors we feed to core #7514 + boolean negative = zoomBy < 0; + zoomBy = MathUtils.clamp(Math.abs(zoomBy), + MapboxConstants.MINIMUM_SCALE_FACTOR_CLAMP, + MapboxConstants.MAXIMUM_SCALE_FACTOR_CLAMP); + return negative ? -zoomBy : zoomBy; + } + return zoomBy; + } + } - 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 final class RotateGestureListener extends RotateGestureDetector.SimpleOnRotateGestureListener { + private PointF rotateFocalPoint; + private final float minimumScaleSpanWhenRotating; + private final float minimumAngularVelocity; - private long beginTime = 0; - private boolean started = false; + RotateGestureListener(float minimumScaleSpanWhenRotating, float minimumAngularVelocity) { + this.minimumScaleSpanWhenRotating = minimumScaleSpanWhenRotating; + this.minimumAngularVelocity = minimumAngularVelocity; + } - // Called when two fingers first touch the screen @Override public boolean onRotateBegin(RotateGestureDetector detector) { - if (!trackingSettings.isRotateGestureCurrentlyEnabled()) { + if (!uiSettings.isRotateGesturesEnabled()) { return false; } - // notify camera change listener + transform.cancelTransitions(); cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE); - beginTime = detector.getEventTime(); - return true; - } + // when rotation starts, interrupting scale and increasing the threshold + // to make rotation without scaling easier + gesturesManager.getStandardScaleGestureDetector().setSpanSinceStartThreshold(minimumScaleSpanWhenRotating); + gesturesManager.getStandardScaleGestureDetector().interrupt(); - // Called each time one of the two fingers moves - // Called for rotation - @Override - public boolean onRotate(RotateGestureDetector detector) { - if (!trackingSettings.isRotateGestureCurrentlyEnabled() || tiltGestureOccurred) { - return false; - } + // setting in #onRotateBegin() as well, because #onRotate() might not get called before #onRotateEnd() + setRotateFocalPoint(detector); - // If rotate is large enough ignore a tap - // Also is zoom already started, don't rotate - float angle = detector.getRotationDegreesDelta(); - if (Math.abs(angle) >= ROTATE_INVOKE_ANGLE) { - if (isZoomValid(transform)) { - MapEventFactory mapEventFactory = new MapEventFactory(); - LatLng latLng = projection.fromScreenLocation(new PointF(detector.getFocusX(), detector.getFocusY())); - MapState rotation = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom()); - rotation.setGesture(Events.ROTATION); - Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, rotation)); - } - started = true; - } + sendTelemetryEvent(Events.ROTATION, rotateFocalPoint); - if (!started) { - return false; - } + notifyOnRotateBeginListeners(detector); - wasClockwiseRotating = detector.getRotationDegreesDelta() > 0; - if (scaleBeginTime != 0) { - rotateGestureOccurred = true; - } + return 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); + @Override + public boolean onRotate(RotateGestureDetector detector, float rotationDegreesSinceLast, + float rotationDegreesSinceFirst) { + // dispatching start even once more if another detector ended, and this one didn't + cameraChangeDispatcher.onCameraMoveStarted(CameraChangeDispatcher.REASON_API_GESTURE); + + setRotateFocalPoint(detector); // Calculate map bearing value - double bearing = transform.getRawBearing() + angle; + double bearing = transform.getRawBearing() + rotationDegreesSinceLast; // Rotate the map - if (focalPoint != null) { - // User provided focal point - transform.setBearing(bearing, focalPoint.x, focalPoint.y); - } else { - // around gesture - transform.setBearing(bearing, detector.getFocusX(), detector.getFocusY()); - } + transform.setBearing(bearing, rotateFocalPoint.x, rotateFocalPoint.y); + + notifyOnRotateListeners(detector); + 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(); + public void onRotateEnd(RotateGestureDetector detector, float velocityX, float velocityY, float angularVelocity) { + cameraChangeDispatcher.onCameraIdle(); + + // resetting default scale threshold values + gesturesManager.getStandardScaleGestureDetector().setSpanSinceStartThreshold( + gesturesManager.getStandardScaleGestureDetector().getDefaultSpanSinceStartThreshold()); + + if (Math.abs(angularVelocity) < minimumAngularVelocity) { return; } - double angularVelocity = calculateVelocityVector(detector); - if (Math.abs(angularVelocity) > 0.001 && rotateGestureOccurred && !rotateAnimating) { - animateRotateVelocity(); - } else if (!rotateAnimating) { - reset(); - } - } + boolean negative = angularVelocity < 0; + angularVelocity = (float) Math.pow(angularVelocity, 2); + angularVelocity = MathUtils.clamp( + angularVelocity, MapboxConstants.MINIMUM_ANGULAR_VELOCITY, MapboxConstants.MAXIMUM_ANGULAR_VELOCITY); - private void reset() { - beginTime = 0; - started = false; - rotateAnimating = false; - rotateGestureOccurred = false; + long animationTime = (long) (Math.log(angularVelocity + 1) * 500); - if (!twoTap) { - cameraChangeDispatcher.onCameraIdle(); + if (negative) { + angularVelocity = -angularVelocity; } - } - private void animateRotateVelocity() { - rotateAnimating = true; - double currentRotation = transform.getRawBearing(); - double rotateAdditionDegrees = calculateVelocityInDegrees(); - createAnimator(currentRotation, rotateAdditionDegrees).start(); - } + rotateAnimator = createRotateAnimator(angularVelocity, animationTime); + scheduleAnimator(rotateAnimator); - private double calculateVelocityVector(RotateGestureDetector detector) { - return ((detector.getFocusX() * velocityTracker.getYVelocity()) - + (detector.getFocusY() * velocityTracker.getXVelocity())) - / (Math.pow(detector.getFocusX(), 2) + Math.pow(detector.getFocusY(), 2)); + notifyOnRotateEndListeners(detector); } - 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; + private void setRotateFocalPoint(RotateGestureDetector detector) { + if (focalPoint != null) { + // User provided focal point + rotateFocalPoint = focalPoint; + } else { + // around gesture + rotateFocalPoint = detector.getFocalPoint(); } - - 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)); + private Animator createRotateAnimator(float angularVelocity, long animationTime) { + ValueAnimator animator = ValueAnimator.ofFloat(angularVelocity, 0f); + animator.setDuration(animationTime); + animator.setInterpolator(new DecelerateInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { - transform.setBearing((Float) animation.getAnimatedValue()); + transform.setBearing( + transform.getRawBearing() + (float) animation.getAnimatedValue(), + rotateFocalPoint.x, rotateFocalPoint.y, + 0L + ); } }); + animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { + transform.cancelTransitions(); cameraChangeDispatcher.onCameraMoveStarted(REASON_API_ANIMATION); } @Override public void onAnimationCancel(Animator animation) { - reset(); + cameraChangeDispatcher.onCameraIdle(); } @Override public void onAnimationEnd(Animator animation) { - reset(); + cameraChangeDispatcher.onCameraIdle(); } }); + return animator; } } - /** - * Responsible for handling 2 finger shove gestures. - */ - private class ShoveGestureListener implements ShoveGestureDetector.OnShoveGestureListener { - - private long beginTime = 0; - private float totalDelta = 0.0f; - + private final class ShoveGestureListener extends ShoveGestureDetector.SimpleOnShoveGestureListener { @Override public boolean onShoveBegin(ShoveGestureDetector detector) { if (!uiSettings.isTiltGesturesEnabled()) { return false; } - // notify camera change listener + transform.cancelTransitions(); cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE); + + sendTelemetryEvent(Events.PITCH, detector.getFocalPoint()); + + // disabling move gesture during shove + gesturesManager.getMoveGestureDetector().setEnabled(false); + + notifyOnShoveBeginListeners(detector); + return true; } @Override - public void onShoveEnd(ShoveGestureDetector detector) { - beginTime = 0; - totalDelta = 0.0f; - tiltGestureOccurred = false; + public boolean onShove(ShoveGestureDetector detector, float deltaPixelsSinceLast, float deltaPixelsSinceStart) { + // dispatching start even once more if another detector ended, and this one didn't + cameraChangeDispatcher.onCameraMoveStarted(CameraChangeDispatcher.REASON_API_GESTURE); + + // Get tilt value (scale and clamp) + double pitch = transform.getTilt(); + pitch -= MapboxConstants.SHOVE_PIXEL_CHANGE_FACTOR * deltaPixelsSinceLast; + pitch = MathUtils.clamp(pitch, MapboxConstants.MINIMUM_TILT, MapboxConstants.MAXIMUM_TILT); + + // Tilt the map + transform.setTilt(pitch); + + notifyOnShoveListeners(detector); + + return true; } @Override - public boolean onShove(ShoveGestureDetector detector) { - if (!uiSettings.isTiltGesturesEnabled()) { - return false; - } + public void onShoveEnd(ShoveGestureDetector detector, float velocityX, float velocityY) { + cameraChangeDispatcher.onCameraIdle(); - // Ignore short touches in case it is a tap - // Also ignore small tilt - long time = detector.getEventTime(); - long interval = time - beginTime; - if (!tiltGestureOccurred && (interval <= ViewConfiguration.getTapTimeout())) { - return false; - } + // re-enabling move gesture + gesturesManager.getMoveGestureDetector().setEnabled(true); - // 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(); - if (isZoomValid(transform)) { - MapEventFactory mapEventFactory = new MapEventFactory(); - LatLng latLng = projection.fromScreenLocation(new PointF(detector.getFocusX(), detector.getFocusY())); - MapState pitch = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom()); - pitch.setGesture(Events.PITCH); - Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, pitch)); - } - } + notifyOnShoveEndListeners(detector); + } + } - if (!tiltGestureOccurred) { + private final class TapGestureListener implements MultiFingerTapGestureDetector.OnMultiFingerTapGestureListener { + @Override + public boolean onMultiFingerTap(MultiFingerTapGestureDetector detector, int pointersCount) { + if (!uiSettings.isZoomGesturesEnabled() || pointersCount != 2) { return false; } - // Get tilt value (scale and clamp) - double pitch = transform.getTilt(); - pitch -= 0.1 * detector.getShovePixelsDelta(); - pitch = Math.max(MapboxConstants.MINIMUM_TILT, Math.min(MapboxConstants.MAXIMUM_TILT, pitch)); + transform.cancelTransitions(); + cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE); + + if (focalPoint != null) { + transform.zoomOut(focalPoint); + } else { + transform.zoomOut(detector.getFocalPoint()); + } - // Tilt the map - transform.setTilt(pitch); return true; } } + private void sendTelemetryEvent(String eventType, PointF focalPoint) { + if (isZoomValid(transform)) { + MapEventFactory mapEventFactory = new MapEventFactory(); + LatLng latLng = projection.fromScreenLocation(focalPoint); + MapState state = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom()); + state.setGesture(eventType); + Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, state)); + } + } + + private boolean isZoomValid(Transform transform) { + if (transform == null) { + return false; + } + double mapZoom = transform.getZoom(); + return mapZoom >= MapboxConstants.MINIMUM_ZOOM && mapZoom <= MapboxConstants.MAXIMUM_ZOOM; + } + + 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(); + } + } + + void notifyOnMoveBeginListeners(MoveGestureDetector detector) { + for (MapboxMap.OnMoveListener listener : onMoveListenerList) { + listener.onMoveBegin(detector); + } + } + + void notifyOnMoveListeners(MoveGestureDetector detector) { + for (MapboxMap.OnMoveListener listener : onMoveListenerList) { + listener.onMove(detector); + } + } + + void notifyOnMoveEndListeners(MoveGestureDetector detector) { + for (MapboxMap.OnMoveListener listener : onMoveListenerList) { + listener.onMoveEnd(detector); + } + } + + void notifyOnRotateBeginListeners(RotateGestureDetector detector) { + for (MapboxMap.OnRotateListener listener : onRotateListenerList) { + listener.onRotateBegin(detector); + } + } + + void notifyOnRotateListeners(RotateGestureDetector detector) { + for (MapboxMap.OnRotateListener listener : onRotateListenerList) { + listener.onRotate(detector); + } + } + + void notifyOnRotateEndListeners(RotateGestureDetector detector) { + for (MapboxMap.OnRotateListener listener : onRotateListenerList) { + listener.onRotateEnd(detector); + } + } + + void notifyOnScaleBeginListeners(StandardScaleGestureDetector detector) { + for (MapboxMap.OnScaleListener listener : onScaleListenerList) { + listener.onScaleBegin(detector); + } + } + + void notifyOnScaleListeners(StandardScaleGestureDetector detector) { + for (MapboxMap.OnScaleListener listener : onScaleListenerList) { + listener.onScale(detector); + } + } + + void notifyOnScaleEndListeners(StandardScaleGestureDetector detector) { + for (MapboxMap.OnScaleListener listener : onScaleListenerList) { + listener.onScaleEnd(detector); + } + } + + void notifyOnShoveBeginListeners(ShoveGestureDetector detector) { + for (MapboxMap.OnShoveListener listener : onShoveListenerList) { + listener.onShoveBegin(detector); + } + } + + void notifyOnShoveListeners(ShoveGestureDetector detector) { + for (MapboxMap.OnShoveListener listener : onShoveListenerList) { + listener.onShove(detector); + } + } + + void notifyOnShoveEndListeners(ShoveGestureDetector detector) { + for (MapboxMap.OnShoveListener listener : onShoveListenerList) { + listener.onShoveEnd(detector); + } + } + void setOnMapClickListener(MapboxMap.OnMapClickListener onMapClickListener) { this.onMapClickListener = onMapClickListener; } @@ -981,14 +972,43 @@ final class MapGestureDetector { onScrollListenerList.remove(onScrollListener); } - private boolean isZoomValid(Transform transform) { - if (transform == null) { - return false; - } - double mapZoom = transform.getZoom(); - if (mapZoom < MapboxConstants.MINIMUM_ZOOM || mapZoom > MapboxConstants.MAXIMUM_ZOOM) { - return false; - } - return true; + void addOnMoveListener(MapboxMap.OnMoveListener listener) { + onMoveListenerList.add(listener); + } + + void removeOnMoveListener(MapboxMap.OnMoveListener listener) { + onMoveListenerList.remove(listener); + } + + void addOnRotateListener(MapboxMap.OnRotateListener listener) { + onRotateListenerList.add(listener); + } + + void removeOnRotateListener(MapboxMap.OnRotateListener listener) { + onRotateListenerList.remove(listener); + } + + void addOnScaleListener(MapboxMap.OnScaleListener listener) { + onScaleListenerList.add(listener); + } + + void removeOnScaleListener(MapboxMap.OnScaleListener listener) { + onScaleListenerList.remove(listener); + } + + void addShoveListener(MapboxMap.OnShoveListener listener) { + onShoveListenerList.add(listener); + } + + void removeShoveListener(MapboxMap.OnShoveListener listener) { + onShoveListenerList.remove(listener); + } + + AndroidGesturesManager getGesturesManager() { + return gesturesManager; + } + + void setGesturesManager(AndroidGesturesManager gesturesManager) { + this.gesturesManager = gesturesManager; } -} +}
\ No newline at end of file |