[android] clear CameraChangeDispatcher message queue when camera move is restarted

This commit is contained in:
Łukasz Paczos
2018-09-25 17:41:43 +02:00
committed by Łukasz Paczos
parent 66e95ac2ab
commit 095ea17c7d
3 changed files with 175 additions and 117 deletions

View File

@@ -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;
@Retention(SOURCE)
@IntDef( {MOVE_STARTED, MOVE, MOVE_CANCELED, IDLE})
@interface CameraChange {
}
// deprecated API
if (onCameraMoveStartedListener != null) {
onCameraMoveStartedListener.onCameraMoveStarted(moveStartedReason);
}
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 (!onCameraMoveStarted.isEmpty()) {
for (OnCameraMoveStartedListener cameraMoveStartedListener : onCameraMoveStarted) {
cameraMoveStartedListener.onCameraMoveStarted(moveStartedReason);
}
}
}
};
@Override
public void onCameraMoveStarted(final int reason) {
moveStartedReason = reason;
handler.scheduleMessage(MOVE_STARTED);
}
private final Runnable onCameraMoveRunnable = new Runnable() {
@Override
public void run() {
// deprecated API
if (onCameraMoveListener != null && !idle) {
onCameraMoveListener.onCameraMove();
}
@Override
public void onCameraMove() {
handler.scheduleMessage(MOVE);
}
// new API
if (!onCameraMove.isEmpty() && !idle) {
for (OnCameraMoveListener cameraMoveListener : onCameraMove) {
cameraMoveListener.onCameraMove();
}
}
}
};
@Override
public void onCameraMoveCanceled() {
handler.scheduleMessage(MOVE_CANCELED);
}
private final Runnable onCameraMoveCancelRunnable = new Runnable() {
@Override
public void run() {
// deprecated API
if (onCameraMoveCanceledListener != null && !idle) {
onCameraMoveCanceledListener.onCameraMoveCanceled();
}
// new API
if (!onCameraMoveCanceled.isEmpty() && !idle) {
for (OnCameraMoveCanceledListener cameraMoveCanceledListener : onCameraMoveCanceled) {
cameraMoveCanceledListener.onCameraMoveCanceled();
}
}
}
};
private final Runnable onCameraIdleRunnable = new Runnable() {
@Override
public void run() {
if (idle) {
return;
}
idle = true;
// deprecated API
if (onCameraIdleListener != null) {
onCameraIdleListener.onCameraIdle();
}
// 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<CameraChangeDispatcher> 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);
}
}
}
}

View File

@@ -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;
}