diff options
author | Łukasz Paczos <lukas.paczos@gmail.com> | 2019-09-03 15:40:09 +0200 |
---|---|---|
committer | Łukasz Paczos <lukas.paczos@gmail.com> | 2019-09-03 15:40:09 +0200 |
commit | 3c8120dca00e2ef61c98515467356cd07e505018 (patch) | |
tree | 53cca69e00f5790a2c656a70281e134ae7f9f8a3 | |
parent | 4ba1270dcc0a51c7c68be2bfbac283ee76ea8680 (diff) | |
download | qtlocation-mapboxgl-3c8120dca00e2ef61c98515467356cd07e505018.tar.gz |
[android][wip] basic animation thread and target animation
7 files changed, 386 insertions, 2 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraTransition.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraTransition.java new file mode 100644 index 0000000000..7132effce1 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraTransition.java @@ -0,0 +1,98 @@ +package com.mapbox.mapboxsdk.maps; + +public abstract class CameraTransition<T> { + + static final int PROPERTY_CENTER = 0; + static final int PROPERTY_ZOOM = 1; + static final int PROPERTY_PITCH = 2; + static final int PROPERTY_BEARING = 3; + static final int PROPERTY_ANCHOR = 4; + static final int PROPERTY_PADDING = 5; + + public static final int TYPE_ANY = 0; + public static final int TYPE_GESTURE = 1; + public static final int TYPE_LOCATION = 2; + + private final int type; + T startValue; + protected final T endValue; + double startTime; + protected final double duration; + private final double delay; + protected final double endTime; + protected boolean canceled; + private boolean isFinishing; + + CameraTransition(int type, double duration, double delay, T endValue) { + this.type = type; + this.startTime = System.currentTimeMillis() + delay; + this.duration = duration; + this.delay = delay; + this.endTime = startTime + duration; + this.endValue = endValue; + } + + public int getType() { + return type; + } + + public void cancel() { + canceled = true; + } + + public T getStartValue() { + return startValue; + } + + void setStartValue(T startValue) { + this.startValue = startValue; + } + + public T getEndValue() { + return endValue; + } + + public boolean isCanceled() { + return canceled; + } + + public double getStartTime() { + return startTime; + } + + void setStartTime(double startTime) { + this.startTime = startTime; + } + + public double getDuration() { + return duration; + } + + public double getDelay() { + return delay; + } + + public double getEndTime() { + return endTime; + } + + abstract int getCameraProperty(); + + abstract T onFrame(double currentTime); + + boolean isFinishing() { + return isFinishing; + } + + void setFinishing() { + isFinishing = true; + } + + void onCancel() { + // todo camera - notify listeners + } + + void onFinish() { + // todo camera - notify listeners + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapCameraController.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapCameraController.java new file mode 100644 index 0000000000..2156b5f744 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapCameraController.java @@ -0,0 +1,227 @@ +package com.mapbox.mapboxsdk.maps; + +import android.os.Handler; +import android.os.Looper; +import android.support.annotation.NonNull; + +import com.mapbox.mapboxsdk.camera.CameraPosition; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +public class MapCameraController { + + @NonNull + private final List<CameraTransition> queuedTransitions = new ArrayList<>(); + + @NonNull + private final HashMap<Integer, CameraTransition> runningTransitions = new HashMap<>(5); + + @NonNull + private CameraBehavior behavior = new DefaultCameraBehavior(); + + @NonNull + private final Handler handler = new Handler(Looper.getMainLooper()); + + @NonNull + private final Transform transform; + + private volatile boolean destroyed; + + @NonNull + private final Thread thread = new Thread(new Runnable() { + @Override + public void run() { + CameraPosition cameraPosition = null; + while (true) { + if (destroyed) { + return; + } + + final CameraPosition update = cameraPosition; + if (!runningTransitions.isEmpty()) { + handler.post(new Runnable() { + @Override + public void run() { + CameraPosition finalUpdate = update; + + Iterator<CameraTransition> iterator = runningTransitions.values().iterator(); + while (iterator.hasNext()) { + CameraTransition transition = iterator.next(); + if (transition.isCanceled()) { + transition.onCancel(); + iterator.remove(); + CameraPosition.Builder builder = new CameraPosition.Builder(finalUpdate); + if (transition.getCameraProperty() == CameraTransition.PROPERTY_CENTER) { + builder.target(null); + } + finalUpdate = builder.build(); + } + } + + // todo camera - check if update is noop, abort then + + if (finalUpdate != null && finalUpdate.target != null) { + // todo camera - remove if-check + transform.moveCamera(finalUpdate); + } + + iterator = runningTransitions.values().iterator(); + while (iterator.hasNext()) { + CameraTransition transition = iterator.next(); + if (transition.isFinishing()) { + transition.onFinish(); + iterator.remove(); + } + } + } + }); + } + + // todo camera - what's the correct delay not to flood and block the main thread? + double nextFrameTime = System.currentTimeMillis() + 5; + + if (!runningTransitions.isEmpty()) { + boolean willRun = false; + for (CameraTransition transition : runningTransitions.values()) { + if (transition.startTime <= nextFrameTime) { + willRun = true; + break; + } + } + + if (willRun) { + CameraPosition.Builder builder = new CameraPosition.Builder(); + + TargetCameraTransition targetCameraTransition = + (TargetCameraTransition) runningTransitions.get(CameraTransition.PROPERTY_CENTER); + if (targetCameraTransition != null && targetCameraTransition.startTime <= nextFrameTime) { + if (nextFrameTime >= targetCameraTransition.endTime) { + builder.target(targetCameraTransition.endValue); + targetCameraTransition.setFinishing(); + } else { + builder.target(targetCameraTransition.onFrame(nextFrameTime)); + } + + cameraPosition = builder.build(); + } + } else { + cameraPosition = null; + } + + } else { + cameraPosition = null; + } + + double sleepTime = nextFrameTime - System.currentTimeMillis(); + int nanos = (int) (sleepTime % 999_999); + + try { + Thread.sleep((long) sleepTime, nanos); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }, "MapCameraController"); + + MapCameraController(@NonNull final Transform transform) { + this.transform = transform; + thread.start(); + } + + public void startTransition(final CameraTransition transition) { + transition.setStartTime(System.currentTimeMillis() + transition.getDelay()); + switch (transition.getCameraProperty()) { + case CameraTransition.PROPERTY_CENTER: + transition.setStartValue(transform.getCameraPosition().target); + break; + } + + behavior.animationScheduled(this, transition); + + final CameraTransition runningTransition = runningTransitions.get(transition.getCameraProperty()); + if (runningTransition != null) { + CameraTransition resultingTransition = behavior.resolve(runningTransition, transition); + if (resultingTransition != transition && resultingTransition != runningTransition) { + throw new UnsupportedOperationException(); + } else if (resultingTransition != transition) { + // todo camera - invoke cancel callback right away + transition.cancel(); + } else { + runningTransition.cancel(); + schedule(transition); + } + } else { + schedule(transition); + } + } + + @NonNull + public List<CameraTransition> getQueuedTransitions() { + return queuedTransitions; + } + + @NonNull + public HashMap<Integer, CameraTransition> getRunningTransitions() { + return runningTransitions; + } + + @NonNull + public CameraBehavior getBehavior() { + return behavior; + } + + public void setBehavior(@NonNull CameraBehavior behavior) { + this.behavior = behavior; + } + + void onDestroy() { + try { + destroyed = true; + thread.join(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + + public interface CameraBehavior { + + void animationScheduled(MapCameraController controller, CameraTransition transition); + + @NonNull + CameraTransition resolve(CameraTransition currentTransition, CameraTransition interruptingTransition); + } + + private void schedule(CameraTransition transition) { + runningTransitions.put(transition.getCameraProperty(), transition); + } + + public static class DefaultCameraBehavior implements CameraBehavior { + + @Override + public void animationScheduled(MapCameraController controller, CameraTransition transition) { + if (transition.getType() == CameraTransition.TYPE_GESTURE) { + for (CameraTransition currentTransition : controller.getRunningTransitions().values()) { + if (currentTransition.getType() != CameraTransition.TYPE_GESTURE) { + currentTransition.cancel(); + } + } + + for (CameraTransition currentTransition : controller.getQueuedTransitions()) { + if (currentTransition.getType() != CameraTransition.TYPE_GESTURE) { + currentTransition.cancel(); + } + } + } + } + + @NonNull + @Override + public CameraTransition resolve(CameraTransition currentTransition, CameraTransition interruptingTransition) { + return interruptingTransition; + } + } +} 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 4521d2ae60..ac74bc8812 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 @@ -80,6 +80,8 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { private ImageView attrView; private ImageView logoView; + private MapCameraController cameraController; + @Nullable private MapGestureDetector mapGestureDetector; @Nullable @@ -169,11 +171,12 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { AnnotationManager annotationManager = new AnnotationManager(this, annotationsArray, iconManager, annotations, markers, polygons, polylines, shapeAnnotations); Transform transform = new Transform(this, nativeMapView, cameraDispatcher); + cameraController = new MapCameraController(transform); // MapboxMap List<MapboxMap.OnDeveloperAnimationListener> developerAnimationListeners = new ArrayList<>(); mapboxMap = new MapboxMap(nativeMapView, transform, uiSettings, proj, registerTouchListener, cameraDispatcher, - developerAnimationListeners); + developerAnimationListeners, cameraController); mapboxMap.injectAnnotationManager(annotationManager); // user input @@ -418,6 +421,10 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { mapCallback.onDestroy(); initialRenderCallback.onDestroy(); + if (cameraController != null) { + cameraController.onDestroy(); + } + if (compassView != null) { // avoid leaking context through animator #13742 compassView.resetAnimation(); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java index acd5093dad..1aec486fbc 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java @@ -61,6 +61,7 @@ public final class MapboxMap { private final UiSettings uiSettings; private final Projection projection; private final Transform transform; + private final MapCameraController cameraController; private final CameraChangeDispatcher cameraChangeDispatcher; private final OnGesturesManagerInteractionListener onGesturesManagerInteractionListener; private final List<Style.OnStyleLoaded> awaitingStyleGetters = new ArrayList<>(); @@ -82,11 +83,13 @@ public final class MapboxMap { MapboxMap(NativeMap map, Transform transform, UiSettings ui, Projection projection, OnGesturesManagerInteractionListener listener, CameraChangeDispatcher cameraChangeDispatcher, - List<OnDeveloperAnimationListener> developerAnimationStartedListeners) { + List<OnDeveloperAnimationListener> developerAnimationStartedListeners, + MapCameraController cameraController) { this.nativeMapView = map; this.uiSettings = ui; this.projection = projection; this.transform = transform; + this.cameraController = cameraController; this.onGesturesManagerInteractionListener = listener; this.cameraChangeDispatcher = cameraChangeDispatcher; this.developerAnimationStartedListeners = developerAnimationStartedListeners; @@ -2388,4 +2391,8 @@ public final class MapboxMap { listener.onDeveloperAnimationStarted(); } } + + public MapCameraController getCameraController() { + return cameraController; + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TargetCameraTransition.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TargetCameraTransition.java new file mode 100644 index 0000000000..ffc8ed32ad --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TargetCameraTransition.java @@ -0,0 +1,29 @@ +package com.mapbox.mapboxsdk.maps; + +import com.mapbox.mapboxsdk.geometry.LatLng; + +public class TargetCameraTransition extends CameraTransition<LatLng> { + + private final LatLng latLng = new LatLng(); + + public TargetCameraTransition(int type, long delay, long duration, LatLng endValue) { + super(type, duration, delay, endValue); + } + + @Override + int getCameraProperty() { + return PROPERTY_CENTER; + } + + @Override + LatLng onFrame(double currentTime) { + double fraction = (currentTime - startTime) / duration; + + latLng.setLatitude(startValue.getLatitude() + + ((endValue.getLatitude() - startValue.getLatitude()) * fraction)); + latLng.setLongitude(startValue.getLongitude() + + ((endValue.getLongitude() - startValue.getLongitude()) * fraction)); + + return latLng; + } +} 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 02cd05545d..460e7a0dac 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 @@ -124,6 +124,15 @@ public final class Transform implements MapView.OnCameraDidChangeListener { } @UiThread + public final void moveCamera(@Nullable CameraPosition cameraPosition) { + if (isValidCameraPosition(cameraPosition)) { + nativeMap.jumpTo(cameraPosition.target, cameraPosition.zoom, cameraPosition.tilt, cameraPosition.bearing, + cameraPosition.padding); + invalidateCameraPosition(); + } + } + + @UiThread final void easeCamera(@NonNull MapboxMap mapboxMap, CameraUpdate update, int durationMs, boolean easingInterpolator, @Nullable final MapboxMap.CancelableCallback callback) { CameraPosition cameraPosition = update.getCameraPosition(mapboxMap); diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapCameraControllerTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapCameraControllerTest.java new file mode 100644 index 0000000000..1d68c9323f --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapCameraControllerTest.java @@ -0,0 +1,7 @@ +package com.mapbox.mapboxsdk.maps; + +import static org.junit.Assert.*; + +public class MapCameraControllerTest { + +}
\ No newline at end of file |