diff options
author | Łukasz Paczos <lukas.paczos@gmail.com> | 2018-12-06 18:03:54 +0100 |
---|---|---|
committer | Łukasz Paczos <lukasz.paczos@mapbox.com> | 2018-12-12 12:31:40 +0100 |
commit | d592889e861fa28776ec41a7d9316e2e3ac1a229 (patch) | |
tree | ddfc89ad1397616ad4be93926e93a70758b490fb /platform/android | |
parent | 2d527c2af7224d03429b8b2ccb7c1712e8ffe12b (diff) | |
download | qtlocation-mapboxgl-d592889e861fa28776ec41a7d9316e2e3ac1a229.tar.gz |
[android] CameraMode transition animation and result listeners
Diffstat (limited to 'platform/android')
8 files changed, 523 insertions, 40 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java index fd8537f23f..b49be5c885 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java @@ -19,7 +19,6 @@ import java.util.List; import static com.mapbox.mapboxsdk.location.LocationComponentConstants.ACCURACY_RADIUS_ANIMATION_DURATION; import static com.mapbox.mapboxsdk.location.LocationComponentConstants.COMPASS_UPDATE_RATE_MS; -import static com.mapbox.mapboxsdk.location.LocationComponentConstants.INSTANT_LOCATION_TRANSITION_THRESHOLD; import static com.mapbox.mapboxsdk.location.LocationComponentConstants.MAX_ANIMATION_DURATION_MS; import static com.mapbox.mapboxsdk.location.LocationComponentConstants.TRANSITION_ANIMATION_DURATION_MS; import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_CAMERA_COMPASS_BEARING; @@ -31,6 +30,7 @@ import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_LAYER_GPS_BE import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_LAYER_LATLNG; import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_TILT; import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_ZOOM; +import static com.mapbox.mapboxsdk.location.Utils.immediateAnimation; final class LocationAnimatorCoordinator { @@ -381,13 +381,4 @@ final class LocationAnimatorCoordinator { void setTrackingAnimationDurationMultiplier(float trackingAnimationDurationMultiplier) { this.durationMultiplier = trackingAnimationDurationMultiplier; } - - private boolean immediateAnimation(LatLng current, @NonNull LatLng target, double zoom) { - // TODO: calculate the value based on the projection - double distance = current.distanceTo(target); - if (zoom > 10) { - distance *= zoom; - } - return distance > INSTANT_LOCATION_TRANSITION_THRESHOLD; - } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationCameraController.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationCameraController.java index a50b510073..0a876fb5cb 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationCameraController.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationCameraController.java @@ -2,6 +2,7 @@ package com.mapbox.mapboxsdk.location; import android.content.Context; import android.graphics.PointF; +import android.location.Location; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; @@ -9,6 +10,8 @@ import android.view.MotionEvent; import com.mapbox.android.gestures.AndroidGesturesManager; import com.mapbox.android.gestures.MoveGestureDetector; import com.mapbox.android.gestures.RotateGestureDetector; +import com.mapbox.mapboxsdk.camera.CameraPosition; +import com.mapbox.mapboxsdk.camera.CameraUpdate; import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.location.modes.CameraMode; @@ -30,6 +33,8 @@ final class LocationCameraController implements MapboxAnimator.OnCameraAnimation private final AndroidGesturesManager initialGesturesManager; private final AndroidGesturesManager internalGesturesManager; + private boolean isTransitioning; + LocationCameraController( Context context, MapboxMap mapboxMap, @@ -76,11 +81,68 @@ final class LocationCameraController implements MapboxAnimator.OnCameraAnimation } void setCameraMode(@CameraMode.Mode int cameraMode) { + setCameraMode(cameraMode, null, null); + } + + void setCameraMode(@CameraMode.Mode final int cameraMode, @Nullable Location lastLocation, + @Nullable OnLocationCameraTransitionListener internalTransitionListener) { final boolean wasTracking = isLocationTracking(); this.cameraMode = cameraMode; mapboxMap.cancelTransitions(); adjustGesturesThresholds(); notifyCameraTrackingChangeListener(wasTracking); + transitionToCurrentLocation(wasTracking, lastLocation, internalTransitionListener); + } + + /** + * Initiates a camera animation to the current location if location tracking was engaged. + * Notifies an internal listener when the transition's finished to invalidate animators and notify external listeners. + */ + private void transitionToCurrentLocation(boolean wasTracking, Location lastLocation, + final OnLocationCameraTransitionListener internalTransitionListener) { + if (!wasTracking && isLocationTracking() && lastLocation != null) { + isTransitioning = true; + LatLng target = new LatLng(lastLocation); + CameraPosition.Builder builder = new CameraPosition.Builder().target(target); + if (isLocationBearingTracking()) { + builder.bearing(cameraMode == CameraMode.TRACKING_GPS_NORTH ? 0 : lastLocation.getBearing()); + } + + CameraUpdate update = CameraUpdateFactory.newCameraPosition(builder.build()); + MapboxMap.CancelableCallback callback = new MapboxMap.CancelableCallback() { + @Override + public void onCancel() { + isTransitioning = false; + if (internalTransitionListener != null) { + internalTransitionListener.onLocationCameraTransitionCanceled(cameraMode); + } + } + + @Override + public void onFinish() { + isTransitioning = false; + if (internalTransitionListener != null) { + internalTransitionListener.onLocationCameraTransitionFinished(cameraMode); + } + } + }; + + CameraPosition currentPosition = mapboxMap.getCameraPosition(); + if (Utils.immediateAnimation(currentPosition.target, target, currentPosition.zoom)) { + mapboxMap.moveCamera( + update, + callback); + } else { + mapboxMap.animateCamera( + update, + (int) LocationComponentConstants.TRANSITION_ANIMATION_DURATION_MS, + callback); + } + } else { + if (internalTransitionListener != null) { + internalTransitionListener.onLocationCameraTransitionFinished(cameraMode); + } + } } int getCameraMode() { @@ -88,21 +150,43 @@ final class LocationCameraController implements MapboxAnimator.OnCameraAnimation } private void setBearing(float bearing) { + if (isTransitioning) { + return; + } + mapboxMap.moveCamera(CameraUpdateFactory.bearingTo(bearing)); onCameraMoveInvalidateListener.onInvalidateCameraMove(); } private void setLatLng(@NonNull LatLng latLng) { + if (isTransitioning) { + return; + } + mapboxMap.moveCamera(CameraUpdateFactory.newLatLng(latLng)); onCameraMoveInvalidateListener.onInvalidateCameraMove(); + + if (adjustFocalPoint) { + PointF focalPoint = mapboxMap.getProjection().toScreenLocation(latLng); + mapboxMap.getUiSettings().setFocalPoint(focalPoint); + adjustFocalPoint = false; + } } private void setZoom(float zoom) { + if (isTransitioning) { + return; + } + mapboxMap.moveCamera(CameraUpdateFactory.zoomTo(zoom)); onCameraMoveInvalidateListener.onInvalidateCameraMove(); } private void setTilt(float tilt) { + if (isTransitioning) { + return; + } + mapboxMap.moveCamera(CameraUpdateFactory.tiltTo(tilt)); onCameraMoveInvalidateListener.onInvalidateCameraMove(); } @@ -114,12 +198,6 @@ final class LocationCameraController implements MapboxAnimator.OnCameraAnimation || cameraMode == CameraMode.TRACKING_GPS || cameraMode == CameraMode.TRACKING_GPS_NORTH) { setLatLng(latLng); - - if (adjustFocalPoint) { - PointF focalPoint = mapboxMap.getProjection().toScreenLocation(latLng); - mapboxMap.getUiSettings().setFocalPoint(focalPoint); - adjustFocalPoint = false; - } } } @@ -153,6 +231,10 @@ final class LocationCameraController implements MapboxAnimator.OnCameraAnimation setTilt(tilt); } + boolean isTransitioning() { + return isTransitioning; + } + private void adjustGesturesThresholds() { if (options.trackingGesturesManagement()) { if (isLocationTracking()) { @@ -179,6 +261,11 @@ final class LocationCameraController implements MapboxAnimator.OnCameraAnimation || cameraMode == CameraMode.TRACKING_GPS_NORTH; } + private boolean isLocationBearingTracking() { + return cameraMode == CameraMode.TRACKING_GPS + || cameraMode == CameraMode.TRACKING_GPS_NORTH; + } + private void notifyCameraTrackingChangeListener(boolean wasTracking) { internalCameraTrackingChangedListener.onCameraTrackingChanged(cameraMode); if (wasTracking && !isLocationTracking()) { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java index 340e84abef..7e312a8158 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java @@ -396,6 +396,10 @@ public final class LocationComponent { /** * Sets the camera mode, which determines how the map camera will track the rendered location. * <p> + * When camera is transitioning to a new mode, it will reject inputs like {@link #zoomWhileTracking(double)} or + * {@link #tiltWhileTracking(double)}. + * Use {@link OnLocationCameraTransitionListener} to listen for the transition state. + * <p> * <ul> * <li>{@link CameraMode#NONE}: No camera tracking</li> * <li>{@link CameraMode#NONE_COMPASS}: Camera does not track location, but does track compass bearing</li> @@ -409,9 +413,65 @@ public final class LocationComponent { * @param cameraMode one of the modes found in {@link CameraMode} */ public void setCameraMode(@CameraMode.Mode int cameraMode) { - locationCameraController.setCameraMode(cameraMode); - boolean isGpsNorth = cameraMode == CameraMode.TRACKING_GPS_NORTH; - locationAnimatorCoordinator.resetAllCameraAnimations(mapboxMap.getCameraPosition(), isGpsNorth); + setCameraMode(cameraMode, null); + } + + /** + * Sets the camera mode, which determines how the map camera will track the rendered location. + * <p> + * When camera is transitioning to a new mode, it will reject inputs like {@link #zoomWhileTracking(double)} or + * {@link #tiltWhileTracking(double)}. + * Use {@link OnLocationCameraTransitionListener} to listen for the transition state. + * <p> + * <ul> + * <li>{@link CameraMode#NONE}: No camera tracking</li> + * <li>{@link CameraMode#NONE_COMPASS}: Camera does not track location, but does track compass bearing</li> + * <li>{@link CameraMode#NONE_GPS}: Camera does not track location, but does track GPS bearing</li> + * <li>{@link CameraMode#TRACKING}: Camera tracks the user location</li> + * <li>{@link CameraMode#TRACKING_COMPASS}: Camera tracks the user location, with bearing provided by a compass</li> + * <li>{@link CameraMode#TRACKING_GPS}: Camera tracks the user location, with normalized bearing</li> + * <li>{@link CameraMode#TRACKING_GPS_NORTH}: Camera tracks the user location, with bearing always set to north</li> + * </ul> + * + * @param cameraMode one of the modes found in {@link CameraMode} + * @param transitionListener callback that's going to be invoked when the transition animation finishes + */ + public void setCameraMode(@CameraMode.Mode int cameraMode, + @Nullable OnLocationCameraTransitionListener transitionListener) { + locationCameraController.setCameraMode(cameraMode, lastLocation, new CameraTransitionListener(transitionListener)); + } + + /** + * Used to reset camera animators and notify listeners when the transition finishes. + */ + private class CameraTransitionListener implements OnLocationCameraTransitionListener { + + private final OnLocationCameraTransitionListener externalListener; + + private CameraTransitionListener(OnLocationCameraTransitionListener externalListener) { + this.externalListener = externalListener; + } + + @Override + public void onLocationCameraTransitionFinished(int cameraMode) { + if (externalListener != null) { + externalListener.onLocationCameraTransitionFinished(cameraMode); + } + reset(cameraMode); + } + + @Override + public void onLocationCameraTransitionCanceled(int cameraMode) { + if (externalListener != null) { + externalListener.onLocationCameraTransitionCanceled(cameraMode); + } + reset(cameraMode); + } + + private void reset(@CameraMode.Mode int cameraMode) { + locationAnimatorCoordinator.resetAllCameraAnimations(mapboxMap.getCameraPosition(), + cameraMode == CameraMode.TRACKING_GPS_NORTH); + } } /** diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/OnLocationCameraTransitionListener.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/OnLocationCameraTransitionListener.java new file mode 100644 index 0000000000..824432efc3 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/OnLocationCameraTransitionListener.java @@ -0,0 +1,24 @@ +package com.mapbox.mapboxsdk.location; + +import com.mapbox.mapboxsdk.location.modes.CameraMode; + +/** + * Callback for {@link CameraMode } transition state. + */ +public interface OnLocationCameraTransitionListener { + /** + * Invoked when the camera mode transition animation has been finished. + * + * @param cameraMode camera mode change that initiated the transition + */ + void onLocationCameraTransitionFinished(@CameraMode.Mode int cameraMode); + + /** + * Invoked when the camera mode transition animation has been canceled. + * <p> + * The camera mode is set regardless of the cancellation of the transition animation. + * + * @param cameraMode camera mode change that initiated the transition + */ + void onLocationCameraTransitionCanceled(@CameraMode.Mode int cameraMode); +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/Utils.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/Utils.java index 8907b93b5f..7a2415ed5d 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/Utils.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/Utils.java @@ -14,8 +14,11 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; +import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.maps.MapboxMap; +import static com.mapbox.mapboxsdk.location.LocationComponentConstants.INSTANT_LOCATION_TRANSITION_THRESHOLD; + public final class Utils { private Utils() { @@ -89,6 +92,15 @@ public final class Utils { return (float) (location.getAccuracy() * (1 / metersPerPixel)); } + static boolean immediateAnimation(LatLng current, @NonNull LatLng target, double zoom) { + // TODO: calculate the value based on the projection + double distance = current.distanceTo(target); + if (zoom > 10) { + distance *= zoom; + } + return distance > INSTANT_LOCATION_TRANSITION_THRESHOLD; + } + /** * Casts the value to an even integer. */ diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationCameraControllerTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationCameraControllerTest.java index 9e26bb1a60..4f950cebac 100644 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationCameraControllerTest.java +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationCameraControllerTest.java @@ -1,20 +1,29 @@ package com.mapbox.mapboxsdk.location; import android.graphics.PointF; +import android.location.Location; import com.mapbox.android.gestures.AndroidGesturesManager; import com.mapbox.android.gestures.MoveGestureDetector; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdate; +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.location.modes.CameraMode; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.maps.Projection; import com.mapbox.mapboxsdk.maps.UiSettings; +import junit.framework.Assert; + import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -413,6 +422,255 @@ public class LocationCameraControllerTest { verify(moveGestureDetector, times(1)).interrupt(); } + @Test + public void transition_locationIsNull() { + MapboxMap mapboxMap = mock(MapboxMap.class); + LocationCameraController camera = buildCamera(mapboxMap); + camera.initializeOptions(mock(LocationComponentOptions.class)); + OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class); + + camera.setCameraMode(CameraMode.TRACKING, null, listener); + Assert.assertEquals(CameraMode.TRACKING, camera.getCameraMode()); + verify(listener).onLocationCameraTransitionFinished(CameraMode.TRACKING); + verify(mapboxMap, times(0)) + .animateCamera(any(CameraUpdate.class), any(Integer.class), any(MapboxMap.CancelableCallback.class)); + } + + @Test + public void transition_notTracking() { + MapboxMap mapboxMap = mock(MapboxMap.class); + LocationCameraController camera = buildCamera(mapboxMap); + camera.initializeOptions(mock(LocationComponentOptions.class)); + OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class); + Location location = mock(Location.class); + + camera.setCameraMode(CameraMode.NONE, location, listener); + verify(listener, times(1)).onLocationCameraTransitionFinished(CameraMode.NONE); + verify(mapboxMap, times(0)) + .animateCamera(any(CameraUpdate.class), any(Integer.class), any(MapboxMap.CancelableCallback.class)); + } + + @Test + public void transition_trackingChanged() { + MapboxMap mapboxMap = mock(MapboxMap.class); + when(mapboxMap.getCameraPosition()).thenReturn(CameraPosition.DEFAULT); + LocationCameraController camera = buildCamera(mapboxMap); + camera.initializeOptions(mock(LocationComponentOptions.class)); + final OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class); + Location location = mock(Location.class); + + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + listener.onLocationCameraTransitionFinished(CameraMode.TRACKING); + return null; + } + }).when(mapboxMap).animateCamera(any(CameraUpdate.class), any(Integer.class), any(MapboxMap.CancelableCallback + .class)); + + camera.setCameraMode(CameraMode.TRACKING, location, listener); + verify(listener).onLocationCameraTransitionFinished(CameraMode.TRACKING); + verify(mapboxMap) + .animateCamera(any(CameraUpdate.class), any(Integer.class), any(MapboxMap.CancelableCallback.class)); + } + + @Test + public void transition_trackingNotChanged() { + MapboxMap mapboxMap = mock(MapboxMap.class); + when(mapboxMap.getCameraPosition()).thenReturn(CameraPosition.DEFAULT); + LocationCameraController camera = buildCamera(mapboxMap); + camera.initializeOptions(mock(LocationComponentOptions.class)); + final OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class); + Location location = mock(Location.class); + + camera.setCameraMode(CameraMode.TRACKING, location, listener); + + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + listener.onLocationCameraTransitionFinished(CameraMode.TRACKING_GPS_NORTH); + return null; + } + }).when(mapboxMap).animateCamera(any(CameraUpdate.class), any(Integer.class), any(MapboxMap.CancelableCallback + .class)); + + camera.setCameraMode(CameraMode.TRACKING_GPS_NORTH, location, listener); + verify(listener, times(1)).onLocationCameraTransitionFinished(CameraMode.TRACKING_GPS_NORTH); + verify(mapboxMap, times(1)) + .animateCamera(any(CameraUpdate.class), any(Integer.class), any(MapboxMap.CancelableCallback.class)); + } + + @Test + public void transition_canceled() { + MapboxMap mapboxMap = mock(MapboxMap.class); + when(mapboxMap.getCameraPosition()).thenReturn(CameraPosition.DEFAULT); + LocationCameraController camera = buildCamera(mapboxMap); + camera.initializeOptions(mock(LocationComponentOptions.class)); + final OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class); + Location location = mock(Location.class); + + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + listener.onLocationCameraTransitionCanceled(CameraMode.TRACKING); + return null; + } + }).when(mapboxMap).animateCamera(any(CameraUpdate.class), any(Integer.class), any(MapboxMap.CancelableCallback + .class)); + + camera.setCameraMode(CameraMode.TRACKING, location, listener); + verify(listener).onLocationCameraTransitionCanceled(CameraMode.TRACKING); + verify(mapboxMap) + .animateCamera(any(CameraUpdate.class), any(Integer.class), any(MapboxMap.CancelableCallback.class)); + } + + @Test + public void transition_mapboxCallbackFinished() { + MapboxMap mapboxMap = mock(MapboxMap.class); + when(mapboxMap.getCameraPosition()).thenReturn(CameraPosition.DEFAULT); + LocationCameraController camera = buildCamera(mapboxMap); + camera.initializeOptions(mock(LocationComponentOptions.class)); + final OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class); + Location location = mock(Location.class); + when(location.getLatitude()).thenReturn(1.0); + when(location.getLongitude()).thenReturn(1.0); + when(location.getBearing()).thenReturn(30f); + when(location.getAltitude()).thenReturn(0.0); + + ArgumentCaptor<MapboxMap.CancelableCallback> callbackCaptor + = ArgumentCaptor.forClass(MapboxMap.CancelableCallback.class); + + camera.setCameraMode(CameraMode.TRACKING, location, listener); + + CameraPosition.Builder builder = new CameraPosition.Builder().target(new LatLng(location)); + verify(mapboxMap).animateCamera( + eq(CameraUpdateFactory.newCameraPosition(builder.build())), + eq((int) LocationComponentConstants.TRANSITION_ANIMATION_DURATION_MS), + callbackCaptor.capture()); + + Assert.assertTrue(camera.isTransitioning()); + + callbackCaptor.getValue().onFinish(); + + Assert.assertFalse(camera.isTransitioning()); + + verify(listener).onLocationCameraTransitionFinished(CameraMode.TRACKING); + } + + @Test + public void transition_mapboxCallbackCanceled() { + MapboxMap mapboxMap = mock(MapboxMap.class); + when(mapboxMap.getCameraPosition()).thenReturn(CameraPosition.DEFAULT); + LocationCameraController camera = buildCamera(mapboxMap); + camera.initializeOptions(mock(LocationComponentOptions.class)); + final OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class); + Location location = mock(Location.class); + when(location.getLatitude()).thenReturn(1.0); + when(location.getLongitude()).thenReturn(1.0); + when(location.getBearing()).thenReturn(30f); + when(location.getAltitude()).thenReturn(0.0); + + ArgumentCaptor<MapboxMap.CancelableCallback> callbackCaptor + = ArgumentCaptor.forClass(MapboxMap.CancelableCallback.class); + + camera.setCameraMode(CameraMode.TRACKING, location, listener); + + CameraPosition.Builder builder = new CameraPosition.Builder().target(new LatLng(location)); + verify(mapboxMap).animateCamera( + eq(CameraUpdateFactory.newCameraPosition(builder.build())), + eq((int) LocationComponentConstants.TRANSITION_ANIMATION_DURATION_MS), + callbackCaptor.capture()); + + Assert.assertTrue(camera.isTransitioning()); + + callbackCaptor.getValue().onCancel(); + + Assert.assertFalse(camera.isTransitioning()); + + verify(listener).onLocationCameraTransitionCanceled(CameraMode.TRACKING); + } + + @Test + public void transition_mapboxAnimateBearing() { + MapboxMap mapboxMap = mock(MapboxMap.class); + when(mapboxMap.getCameraPosition()).thenReturn(CameraPosition.DEFAULT); + LocationCameraController camera = buildCamera(mapboxMap); + camera.initializeOptions(mock(LocationComponentOptions.class)); + final OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class); + Location location = mock(Location.class); + when(location.getLatitude()).thenReturn(1.0); + when(location.getLongitude()).thenReturn(1.0); + when(location.getBearing()).thenReturn(30f); + when(location.getAltitude()).thenReturn(0.0); + + camera.setCameraMode(CameraMode.TRACKING_GPS, location, listener); + + CameraPosition.Builder builder = new CameraPosition.Builder().target(new LatLng(location)).bearing(30); + verify(mapboxMap).animateCamera( + eq(CameraUpdateFactory.newCameraPosition(builder.build())), + eq((int) LocationComponentConstants.TRANSITION_ANIMATION_DURATION_MS), + any(MapboxMap.CancelableCallback.class)); + } + + @Test + public void transition_mapboxAnimateNorth() { + MapboxMap mapboxMap = mock(MapboxMap.class); + when(mapboxMap.getCameraPosition()).thenReturn(CameraPosition.DEFAULT); + LocationCameraController camera = buildCamera(mapboxMap); + camera.initializeOptions(mock(LocationComponentOptions.class)); + final OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class); + Location location = mock(Location.class); + when(location.getLatitude()).thenReturn(1.0); + when(location.getLongitude()).thenReturn(1.0); + when(location.getBearing()).thenReturn(30f); + when(location.getAltitude()).thenReturn(0.0); + + camera.setCameraMode(CameraMode.TRACKING_GPS_NORTH, location, listener); + + CameraPosition.Builder builder = new CameraPosition.Builder().target(new LatLng(location)).bearing(0); + verify(mapboxMap).animateCamera( + eq(CameraUpdateFactory.newCameraPosition(builder.build())), + eq((int) LocationComponentConstants.TRANSITION_ANIMATION_DURATION_MS), + any(MapboxMap.CancelableCallback.class)); + } + + @Test + public void transition_animatorValuesDuringTransition() { + MapboxMap mapboxMap = mock(MapboxMap.class); + when(mapboxMap.getCameraPosition()).thenReturn(CameraPosition.DEFAULT); + LocationCameraController camera = buildCamera(mapboxMap); + camera.initializeOptions(mock(LocationComponentOptions.class)); + final OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class); + Location location = mock(Location.class); + + ArgumentCaptor<MapboxMap.CancelableCallback> callbackCaptor + = ArgumentCaptor.forClass(MapboxMap.CancelableCallback.class); + + camera.setCameraMode(CameraMode.TRACKING_GPS, location, listener); + + verify(mapboxMap).animateCamera( + any(CameraUpdate.class), + eq((int) LocationComponentConstants.TRANSITION_ANIMATION_DURATION_MS), + callbackCaptor.capture()); + + camera.onNewLatLngValue(new LatLng(10, 10)); + camera.onNewGpsBearingValue(10f); + camera.onNewCompassBearingValue(10f); + camera.onNewTiltValue(10f); + camera.onNewZoomValue(10f); + + verify(mapboxMap, times(0)).moveCamera(any(CameraUpdate.class)); + + callbackCaptor.getValue().onFinish(); + + camera.onNewLatLngValue(new LatLng(10, 10)); + camera.onNewGpsBearingValue(10f); + camera.onNewTiltValue(10f); + camera.onNewZoomValue(10f); + + verify(mapboxMap, times(4)).moveCamera(any(CameraUpdate.class)); + } + private LocationCameraController buildCamera(OnCameraTrackingChangedListener onCameraTrackingChangedListener) { MapboxMap mapboxMap = mock(MapboxMap.class); when(mapboxMap.getUiSettings()).thenReturn(mock(UiSettings.class)); diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationComponentTest.kt b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationComponentTest.kt index b0bea5aa0e..2fa340fbb8 100644 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationComponentTest.kt +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationComponentTest.kt @@ -7,6 +7,8 @@ import android.os.Looper import com.mapbox.android.core.location.LocationEngine import com.mapbox.android.core.location.LocationEngineRequest import com.mapbox.mapboxsdk.R +import com.mapbox.mapboxsdk.camera.CameraPosition +import com.mapbox.mapboxsdk.location.modes.CameraMode import com.mapbox.mapboxsdk.maps.MapboxMap import com.mapbox.mapboxsdk.maps.Style import io.mockk.mockk @@ -14,7 +16,7 @@ import org.junit.Assert import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers +import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.* import org.mockito.MockitoAnnotations @@ -90,7 +92,7 @@ class LocationComponentTest { .getDimension(R.dimen.mapbox_locationComponentTrackingMultiFingerMoveThreshold) doReturn(0f).`when`(resources) .getDimension(R.dimen.mapbox_locationComponentTrackingMultiFingerMoveThreshold) - locationComponent.activateLocationComponent(context, mockk(),true, locationEngineRequest) + locationComponent.activateLocationComponent(context, mockk(), true, locationEngineRequest) Assert.assertEquals(locationEngineRequest, locationComponent.locationEngineRequest) } @@ -145,4 +147,40 @@ class LocationComponentTest { verify(locationEngine).getLastLocation(lastListener) } + + @Test + fun transitionCallbackFinishedTest() { + locationComponent.activateLocationComponent(context, mockk(), locationEngine, locationEngineRequest, locationComponentOptions) + locationComponent.onStart() + locationComponent.isLocationComponentEnabled = true + `when`(mapboxMap.cameraPosition).thenReturn(CameraPosition.DEFAULT) + + val listener = mock(OnLocationCameraTransitionListener::class.java) + + val callback = ArgumentCaptor.forClass(OnLocationCameraTransitionListener::class.java) + locationComponent.setCameraMode(CameraMode.TRACKING, listener) + verify(locationCameraController).setCameraMode(eq(CameraMode.TRACKING), any(), callback.capture()) + callback.value.onLocationCameraTransitionFinished(CameraMode.TRACKING) + + verify(listener).onLocationCameraTransitionFinished(CameraMode.TRACKING) + verify(locationAnimatorCoordinator).resetAllCameraAnimations(CameraPosition.DEFAULT, false) + } + + @Test + fun transitionCallbackCanceledTest() { + locationComponent.activateLocationComponent(context, mockk(), locationEngine, locationEngineRequest, locationComponentOptions) + locationComponent.onStart() + locationComponent.isLocationComponentEnabled = true + `when`(mapboxMap.cameraPosition).thenReturn(CameraPosition.DEFAULT) + + val listener = mock(OnLocationCameraTransitionListener::class.java) + + val callback = ArgumentCaptor.forClass(OnLocationCameraTransitionListener::class.java) + locationComponent.setCameraMode(CameraMode.TRACKING, listener) + verify(locationCameraController).setCameraMode(eq(CameraMode.TRACKING), any(), callback.capture()) + callback.value.onLocationCameraTransitionCanceled(CameraMode.TRACKING) + + verify(listener).onLocationCameraTransitionCanceled(CameraMode.TRACKING) + verify(locationAnimatorCoordinator).resetAllCameraAnimations(CameraPosition.DEFAULT, false) + } }
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/location/LocationModesActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/location/LocationModesActivity.java index 068aa487d2..618032f5e0 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/location/LocationModesActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/location/LocationModesActivity.java @@ -19,6 +19,7 @@ import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.location.LocationComponent; import com.mapbox.mapboxsdk.location.LocationComponentOptions; import com.mapbox.mapboxsdk.location.OnCameraTrackingChangedListener; +import com.mapbox.mapboxsdk.location.OnLocationCameraTransitionListener; import com.mapbox.mapboxsdk.location.OnLocationClickListener; import com.mapbox.mapboxsdk.location.modes.CameraMode; import com.mapbox.mapboxsdk.location.modes.RenderMode; @@ -341,35 +342,47 @@ public class LocationModesActivity extends AppCompatActivity implements OnMapRea String selectedTrackingType = trackingTypes.get(position); locationTrackingBtn.setText(selectedTrackingType); if (selectedTrackingType.contentEquals("None")) { - locationComponent.setCameraMode(CameraMode.NONE); + setCameraTrackingMode(CameraMode.NONE); } else if (selectedTrackingType.contentEquals("Tracking")) { - locationComponent.setCameraMode(CameraMode.TRACKING); + setCameraTrackingMode(CameraMode.TRACKING); } else if (selectedTrackingType.contentEquals("Tracking Compass")) { - locationComponent.setCameraMode(CameraMode.TRACKING_COMPASS); + setCameraTrackingMode(CameraMode.TRACKING_COMPASS); } else if (selectedTrackingType.contentEquals("Tracking GPS")) { - locationComponent.setCameraMode(CameraMode.TRACKING_GPS); + setCameraTrackingMode(CameraMode.TRACKING_GPS); } else if (selectedTrackingType.contentEquals("Tracking GPS North")) { - locationComponent.setCameraMode(CameraMode.TRACKING_GPS_NORTH); + setCameraTrackingMode(CameraMode.TRACKING_GPS_NORTH); } listPopup.dismiss(); + }); + listPopup.show(); + } - if (locationComponent.getCameraMode() != CameraMode.NONE) { - locationComponent.zoomWhileTracking(15, 750, new MapboxMap.CancelableCallback() { - @Override - public void onCancel() { - // No impl - } + private void setCameraTrackingMode(@CameraMode.Mode int mode) { + locationComponent.setCameraMode(mode, new OnLocationCameraTransitionListener() { + @Override + public void onLocationCameraTransitionFinished(@CameraMode.Mode int cameraMode) { + if (mode != CameraMode.NONE) { + locationComponent.zoomWhileTracking(15, 750, new MapboxMap.CancelableCallback() { + @Override + public void onCancel() { + // No impl + } + + @Override + public void onFinish() { + locationComponent.tiltWhileTracking(45); + } + }); + } else { + mapboxMap.easeCamera(CameraUpdateFactory.tiltTo(0)); + } + } - @Override - public void onFinish() { - locationComponent.tiltWhileTracking(45); - } - }); - } else { - mapboxMap.easeCamera(CameraUpdateFactory.tiltTo(0)); + @Override + public void onLocationCameraTransitionCanceled(@CameraMode.Mode int cameraMode) { + // No impl } }); - listPopup.show(); } @Override |