From 095ea17c7d04a7af6ec3d5bc6d1fb7035e67799f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Paczos?= Date: Tue, 25 Sep 2018 17:41:43 +0200 Subject: [android] clear CameraChangeDispatcher message queue when camera move is restarted --- .../mapboxsdk/maps/CameraChangeDispatcher.java | 243 +++++++++++++-------- .../mapbox/mapboxsdk/maps/MapGestureDetector.java | 47 ++-- .../activity/camera/CameraPositionActivity.java | 2 + 3 files changed, 175 insertions(+), 117 deletions(-) (limited to 'platform') diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java index e558a5d707..a87a290fd7 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java @@ -1,14 +1,19 @@ package com.mapbox.mapboxsdk.maps; import android.os.Handler; +import android.os.Message; +import android.support.annotation.IntDef; import android.support.annotation.NonNull; +import java.lang.annotation.Retention; +import java.lang.ref.WeakReference; import java.util.concurrent.CopyOnWriteArrayList; import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraIdleListener; import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveCanceledListener; import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveListener; import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener; +import static java.lang.annotation.RetentionPolicy.SOURCE; /** * Class responsible for dispatching camera change events to registered listeners. @@ -16,7 +21,7 @@ import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener; class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, MapboxMap.OnCameraMoveListener, MapboxMap.OnCameraMoveCanceledListener, OnCameraIdleListener { - private final Handler handler = new Handler(); + private final CameraChangeHandler handler = new CameraChangeHandler(this); private boolean idle = true; private int moveStartedReason; @@ -31,83 +36,36 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M private OnCameraMoveListener onCameraMoveListener; private OnCameraIdleListener onCameraIdleListener; - private final Runnable onCameraMoveStartedRunnable = new Runnable() { - @Override - public void run() { - if (!idle) { - return; - } - idle = false; - - // deprecated API - if (onCameraMoveStartedListener != null) { - onCameraMoveStartedListener.onCameraMoveStarted(moveStartedReason); - } - - // new API - if (!onCameraMoveStarted.isEmpty()) { - for (OnCameraMoveStartedListener cameraMoveStartedListener : onCameraMoveStarted) { - cameraMoveStartedListener.onCameraMoveStarted(moveStartedReason); - } - } - } - }; - - private final Runnable onCameraMoveRunnable = new Runnable() { - @Override - public void run() { - // deprecated API - if (onCameraMoveListener != null && !idle) { - onCameraMoveListener.onCameraMove(); - } - - // new API - if (!onCameraMove.isEmpty() && !idle) { - for (OnCameraMoveListener cameraMoveListener : onCameraMove) { - cameraMoveListener.onCameraMove(); - } - } - } - }; + @Retention(SOURCE) + @IntDef( {MOVE_STARTED, MOVE, MOVE_CANCELED, IDLE}) + @interface CameraChange { + } - private final Runnable onCameraMoveCancelRunnable = new Runnable() { - @Override - public void run() { - // deprecated API - if (onCameraMoveCanceledListener != null && !idle) { - onCameraMoveCanceledListener.onCameraMoveCanceled(); - } + private static final int MOVE_STARTED = 0; + private static final int MOVE = 1; + private static final int MOVE_CANCELED = 2; + private static final int IDLE = 3; - // new API - if (!onCameraMoveCanceled.isEmpty() && !idle) { - for (OnCameraMoveCanceledListener cameraMoveCanceledListener : onCameraMoveCanceled) { - cameraMoveCanceledListener.onCameraMoveCanceled(); - } - } - } - }; + @Override + public void onCameraMoveStarted(final int reason) { + moveStartedReason = reason; + handler.scheduleMessage(MOVE_STARTED); + } - private final Runnable onCameraIdleRunnable = new Runnable() { - @Override - public void run() { - if (idle) { - return; - } - idle = true; + @Override + public void onCameraMove() { + handler.scheduleMessage(MOVE); + } - // deprecated API - if (onCameraIdleListener != null) { - onCameraIdleListener.onCameraIdle(); - } + @Override + public void onCameraMoveCanceled() { + handler.scheduleMessage(MOVE_CANCELED); + } - // new API - if (!onCameraIdle.isEmpty()) { - for (OnCameraIdleListener cameraIdleListener : onCameraIdle) { - cameraIdleListener.onCameraIdle(); - } - } - } - }; + @Override + public void onCameraIdle() { + handler.scheduleMessage(IDLE); + } @Deprecated void setOnCameraMoveStartedListener(OnCameraMoveStartedListener onCameraMoveStartedListener) { @@ -129,27 +87,6 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M this.onCameraIdleListener = onCameraIdleListener; } - @Override - public void onCameraMoveStarted(final int reason) { - moveStartedReason = reason; - handler.post(onCameraMoveStartedRunnable); - } - - @Override - public void onCameraMove() { - handler.post(onCameraMoveRunnable); - } - - @Override - public void onCameraMoveCanceled() { - handler.post(onCameraMoveCancelRunnable); - } - - @Override - public void onCameraIdle() { - handler.post(onCameraIdleRunnable); - } - void addOnCameraIdleListener(@NonNull OnCameraIdleListener listener) { onCameraIdle.add(listener); } @@ -189,4 +126,122 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M onCameraMove.remove(listener); } } + + private void executeOnCameraMoveStarted() { + if (!idle) { + return; + } + idle = false; + + // deprecated API + if (onCameraMoveStartedListener != null) { + onCameraMoveStartedListener.onCameraMoveStarted(moveStartedReason); + } + + // new API + if (!onCameraMoveStarted.isEmpty()) { + for (OnCameraMoveStartedListener cameraMoveStartedListener : onCameraMoveStarted) { + cameraMoveStartedListener.onCameraMoveStarted(moveStartedReason); + } + } + } + + private void executeOnCameraMove() { + // deprecated API + if (onCameraMoveListener != null && !idle) { + onCameraMoveListener.onCameraMove(); + } + + // new API + if (!onCameraMove.isEmpty() && !idle) { + for (OnCameraMoveListener cameraMoveListener : onCameraMove) { + cameraMoveListener.onCameraMove(); + } + } + } + + private void executeOnCameraMoveCancelled() { + // deprecated API + if (onCameraMoveCanceledListener != null && !idle) { + onCameraMoveCanceledListener.onCameraMoveCanceled(); + } + + // new API + if (!onCameraMoveCanceled.isEmpty() && !idle) { + for (OnCameraMoveCanceledListener cameraMoveCanceledListener : onCameraMoveCanceled) { + cameraMoveCanceledListener.onCameraMoveCanceled(); + } + } + } + + private void executeOnCameraIdle() { + if (idle) { + return; + } + idle = true; + + // deprecated API + if (onCameraIdleListener != null) { + onCameraIdleListener.onCameraIdle(); + } + + // new API + if (!onCameraIdle.isEmpty()) { + for (OnCameraIdleListener cameraIdleListener : onCameraIdle) { + cameraIdleListener.onCameraIdle(); + } + } + } + + private static class CameraChangeHandler extends Handler { + + private WeakReference dispatcherWeakReference; + + CameraChangeHandler(CameraChangeDispatcher dispatcher) { + super(); + this.dispatcherWeakReference = new WeakReference<>(dispatcher); + } + + @Override + public void handleMessage(Message msg) { + CameraChangeDispatcher dispatcher = dispatcherWeakReference.get(); + if (dispatcher != null) { + switch (msg.what) { + case MOVE_STARTED: + dispatcher.executeOnCameraMoveStarted(); + break; + case MOVE: + dispatcher.executeOnCameraMove(); + break; + case MOVE_CANCELED: + dispatcher.executeOnCameraMoveCancelled(); + break; + case IDLE: + dispatcher.executeOnCameraIdle(); + break; + } + } + } + + void scheduleMessage(@CameraChange int change) { + CameraChangeDispatcher dispatcher = dispatcherWeakReference.get(); + if (dispatcher != null) { + // if there is a movement that is cancelled/stopped and restarted in the same code block + // we can safely assume that the movement will continue, no need for dispatching unnecessary callbacks sequence + if (change == MOVE_STARTED) { + boolean shouldReturn = !dispatcher.idle && (hasMessages(IDLE) || hasMessages(MOVE_CANCELED)); + removeMessages(IDLE); + removeMessages(MOVE_CANCELED); + + if (shouldReturn) { + return; + } + } + + Message message = new Message(); + message.what = change; + this.sendMessage(message); + } + } + } } 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 977afe4bd9..b225f35c83 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 @@ -10,6 +10,7 @@ import android.support.annotation.Nullable; import android.view.InputDevice; import android.view.MotionEvent; import android.view.animation.DecelerateInterpolator; + import com.mapbox.android.gestures.AndroidGesturesManager; import com.mapbox.android.gestures.Constants; import com.mapbox.android.gestures.MoveGestureDetector; @@ -222,16 +223,21 @@ final class MapGestureDetector { case MotionEvent.ACTION_UP: transform.setGestureInProgress(false); - // Start all awaiting velocity animations - animationsTimeoutHandler.removeCallbacksAndMessages(null); - for (Animator animator : scheduledAnimators) { - animator.start(); + if (scheduledAnimators.isEmpty()) { + cameraChangeDispatcher.onCameraIdle(); + } else { + // Start all awaiting velocity animations + animationsTimeoutHandler.removeCallbacksAndMessages(null); + for (Animator animator : scheduledAnimators) { + animator.start(); + } + scheduledAnimators.clear(); } - scheduledAnimators.clear(); break; case MotionEvent.ACTION_CANCEL: scheduledAnimators.clear(); + transform.cancelTransitions(); transform.setGestureInProgress(false); break; } @@ -526,8 +532,6 @@ final class MapGestureDetector { @Override public void onScaleEnd(StandardScaleGestureDetector detector, float velocityX, float velocityY) { - cameraChangeDispatcher.onCameraIdle(); - if (quickZoom) { //if quickzoom, re-enabling move gesture detector gesturesManager.getMoveGestureDetector().setEnabled(true); @@ -542,18 +546,19 @@ final class MapGestureDetector { notifyOnScaleEndListeners(detector); - if (!uiSettings.isScaleVelocityAnimationEnabled()) { + float velocityXY = Math.abs(velocityX) + Math.abs(velocityY); + + if (!uiSettings.isScaleVelocityAnimationEnabled() || velocityXY < minimumVelocity) { + // notifying listeners that camera is idle only if there is no follow-up animation + cameraChangeDispatcher.onCameraIdle(); return; } - 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.abs(zoomAddition) * 1000 / 4); - scaleAnimator = createScaleAnimator(currentZoom, zoomAddition, scaleFocalPoint, animationTime); - scheduleAnimator(scaleAnimator); - } + double zoomAddition = calculateScale(velocityXY, detector.isScalingOut()); + double currentZoom = transform.getRawZoom(); + long animationTime = (long) (Math.abs(zoomAddition) * 1000 / 4); + scaleAnimator = createScaleAnimator(currentZoom, zoomAddition, scaleFocalPoint, animationTime); + scheduleAnimator(scaleAnimator); } private void setScaleFocalPoint(StandardScaleGestureDetector detector) { @@ -650,8 +655,6 @@ final class MapGestureDetector { @Override public void onRotateEnd(RotateGestureDetector detector, float velocityX, float velocityY, float angularVelocity) { - cameraChangeDispatcher.onCameraIdle(); - if (uiSettings.isIncreaseScaleThresholdWhenRotating()) { // resetting default scale threshold values gesturesManager.getStandardScaleGestureDetector().setSpanSinceStartThreshold(defaultSpanSinceStartThreshold); @@ -659,11 +662,9 @@ final class MapGestureDetector { notifyOnRotateEndListeners(detector); - if (!uiSettings.isRotateVelocityAnimationEnabled()) { - return; - } - - if (Math.abs(angularVelocity) < minimumAngularVelocity) { + if (!uiSettings.isRotateVelocityAnimationEnabled() || Math.abs(angularVelocity) < minimumAngularVelocity) { + // notifying listeners that camera is idle only if there is no follow-up animation + cameraChangeDispatcher.onCameraIdle(); return; } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraPositionActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraPositionActivity.java index 89c8a68abd..42711a4379 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraPositionActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraPositionActivity.java @@ -13,6 +13,7 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.SeekBar; import android.widget.TextView; + import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.geometry.LatLng; @@ -20,6 +21,7 @@ import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.testapp.R; + import timber.log.Timber; /** -- cgit v1.2.1