[android] immediate location animation based on map's projection

This commit is contained in:
Łukasz Paczos
2018-12-11 19:00:15 +01:00
committed by Łukasz Paczos
parent bbeec8dacb
commit e86d1b2ac9
8 changed files with 89 additions and 15 deletions

View File

@@ -13,6 +13,7 @@ import android.view.animation.LinearInterpolator;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.Projection;
import java.util.ArrayList;
import java.util.List;
@@ -39,12 +40,17 @@ final class LocationAnimatorCoordinator {
final List<MapboxAnimator.OnLayerAnimationsValuesChangeListener> layerListeners = new ArrayList<>();
final List<MapboxAnimator.OnCameraAnimationsValuesChangeListener> cameraListeners = new ArrayList<>();
private final Projection projection;
private Location previousLocation;
private float previousAccuracyRadius = -1;
private float previousCompassBearing = -1;
private long locationUpdateTimestamp = -1;
private float durationMultiplier;
LocationAnimatorCoordinator(Projection projection) {
this.projection = projection;
}
void addLayerListener(MapboxAnimator.OnLayerAnimationsValuesChangeListener listener) {
layerListeners.add(listener);
}
@@ -81,8 +87,8 @@ final class LocationAnimatorCoordinator {
updateLayerAnimators(previousLayerLatLng, targetLatLng, previousLayerBearing, targetLayerBearing);
updateCameraAnimators(previousCameraLatLng, previousCameraBearing, targetLatLng, targetCameraBearing);
boolean snap = immediateAnimation(previousCameraLatLng, targetLatLng, currentCameraPosition.zoom)
|| immediateAnimation(previousLayerLatLng, targetLatLng, currentCameraPosition.zoom);
boolean snap = immediateAnimation(projection, previousCameraLatLng, targetLatLng)
|| immediateAnimation(projection, previousLayerLatLng, targetLatLng);
playLocationAnimators(snap ? 0 : getAnimationDuration());
previousLocation = newLocation;
@@ -317,7 +323,7 @@ final class LocationAnimatorCoordinator {
createNewAnimator(ANIMATOR_CAMERA_LATLNG,
new CameraLatLngAnimator(previousCameraTarget, currentTarget, cameraListeners));
return immediateAnimation(previousCameraTarget, currentTarget, currentCameraPosition.zoom);
return immediateAnimation(projection, previousCameraTarget, currentTarget);
}
private void resetCameraGpsBearingAnimation(@NonNull CameraPosition currentCameraPosition, boolean isGpsNorth) {

View File

@@ -129,7 +129,7 @@ final class LocationCameraController implements MapboxAnimator.OnCameraAnimation
};
CameraPosition currentPosition = mapboxMap.getCameraPosition();
if (Utils.immediateAnimation(currentPosition.target, target, currentPosition.zoom)) {
if (Utils.immediateAnimation(mapboxMap.getProjection(), currentPosition.target, target)) {
mapboxMap.moveCamera(
update,
callback);

View File

@@ -1015,7 +1015,7 @@ public final class LocationComponent {
locationCameraController = new LocationCameraController(
context, mapboxMap, cameraTrackingChangedListener, options, onCameraMoveInvalidateListener);
locationAnimatorCoordinator = new LocationAnimatorCoordinator();
locationAnimatorCoordinator = new LocationAnimatorCoordinator(mapboxMap.getProjection());
locationAnimatorCoordinator.addLayerListener(locationLayerController);
locationAnimatorCoordinator.addCameraListener(locationCameraController);
locationAnimatorCoordinator.setTrackingAnimationDurationMultiplier(options

View File

@@ -24,7 +24,7 @@ final class LocationComponentConstants {
static final long DEFAULT_TRACKING_TILT_ANIM_DURATION = 1250;
// Threshold value to perform immediate camera/layer position update.
static final double INSTANT_LOCATION_TRANSITION_THRESHOLD = 500_000;
static final double INSTANT_LOCATION_TRANSITION_THRESHOLD = 50_000;
// Default interval between location updates
static final long DEFAULT_INTERVAL_MILLIS = 1000;

View File

@@ -16,6 +16,7 @@ import android.support.v4.content.ContextCompat;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.Projection;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.INSTANT_LOCATION_TRANSITION_THRESHOLD;
@@ -92,13 +93,10 @@ 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
static boolean immediateAnimation(@NonNull Projection projection, @NonNull LatLng current, @NonNull LatLng target) {
double metersPerPixel = projection.getMetersPerPixelAtLatitude((current.getLatitude() + target.getLatitude()) / 2);
double distance = current.distanceTo(target);
if (zoom > 10) {
distance *= zoom;
}
return distance > INSTANT_LOCATION_TRANSITION_THRESHOLD;
return distance / metersPerPixel > INSTANT_LOCATION_TRANSITION_THRESHOLD;
}
/**

View File

@@ -6,12 +6,14 @@ import com.mapbox.mapboxsdk.geometry.LatLng
import com.mapbox.mapboxsdk.location.LocationComponentConstants.DEFAULT_TRACKING_TILT_ANIM_DURATION
import com.mapbox.mapboxsdk.location.LocationComponentConstants.DEFAULT_TRACKING_ZOOM_ANIM_DURATION
import com.mapbox.mapboxsdk.location.MapboxAnimator.*
import com.mapbox.mapboxsdk.maps.Projection
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
@@ -22,7 +24,7 @@ class LocationAnimatorCoordinatorTest {
@Before
fun setUp() {
locationAnimatorCoordinator = LocationAnimatorCoordinator()
locationAnimatorCoordinator = LocationAnimatorCoordinator(mock(Projection::class.java))
}
@Test

View File

@@ -454,6 +454,9 @@ public class LocationCameraControllerTest {
public void transition_trackingChanged() {
MapboxMap mapboxMap = mock(MapboxMap.class);
when(mapboxMap.getCameraPosition()).thenReturn(CameraPosition.DEFAULT);
Projection projection = mock(Projection.class);
when(mapboxMap.getProjection()).thenReturn(projection);
when(projection.getMetersPerPixelAtLatitude(any(Double.class))).thenReturn(Double.valueOf(1000));
LocationCameraController camera = buildCamera(mapboxMap);
camera.initializeOptions(mock(LocationComponentOptions.class));
final OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class);
@@ -478,6 +481,9 @@ public class LocationCameraControllerTest {
public void transition_trackingNotChanged() {
MapboxMap mapboxMap = mock(MapboxMap.class);
when(mapboxMap.getCameraPosition()).thenReturn(CameraPosition.DEFAULT);
Projection projection = mock(Projection.class);
when(mapboxMap.getProjection()).thenReturn(projection);
when(projection.getMetersPerPixelAtLatitude(any(Double.class))).thenReturn(Double.valueOf(1000));
LocationCameraController camera = buildCamera(mapboxMap);
camera.initializeOptions(mock(LocationComponentOptions.class));
final OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class);
@@ -504,6 +510,9 @@ public class LocationCameraControllerTest {
public void transition_canceled() {
MapboxMap mapboxMap = mock(MapboxMap.class);
when(mapboxMap.getCameraPosition()).thenReturn(CameraPosition.DEFAULT);
Projection projection = mock(Projection.class);
when(mapboxMap.getProjection()).thenReturn(projection);
when(projection.getMetersPerPixelAtLatitude(any(Double.class))).thenReturn(Double.valueOf(1000));
LocationCameraController camera = buildCamera(mapboxMap);
camera.initializeOptions(mock(LocationComponentOptions.class));
final OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class);
@@ -528,6 +537,9 @@ public class LocationCameraControllerTest {
public void transition_mapboxCallbackFinished() {
MapboxMap mapboxMap = mock(MapboxMap.class);
when(mapboxMap.getCameraPosition()).thenReturn(CameraPosition.DEFAULT);
Projection projection = mock(Projection.class);
when(mapboxMap.getProjection()).thenReturn(projection);
when(projection.getMetersPerPixelAtLatitude(any(Double.class))).thenReturn(Double.valueOf(1000));
LocationCameraController camera = buildCamera(mapboxMap);
camera.initializeOptions(mock(LocationComponentOptions.class));
final OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class);
@@ -557,10 +569,48 @@ public class LocationCameraControllerTest {
verify(listener).onLocationCameraTransitionFinished(CameraMode.TRACKING);
}
@Test
public void transition_mapboxCallbackFinishedImmediately() {
MapboxMap mapboxMap = mock(MapboxMap.class);
when(mapboxMap.getCameraPosition()).thenReturn(CameraPosition.DEFAULT);
Projection projection = mock(Projection.class);
when(mapboxMap.getProjection()).thenReturn(projection);
when(projection.getMetersPerPixelAtLatitude(any(Double.class))).thenReturn(Double.valueOf(1));
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).moveCamera(
eq(CameraUpdateFactory.newCameraPosition(builder.build())),
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);
Projection projection = mock(Projection.class);
when(mapboxMap.getProjection()).thenReturn(projection);
when(projection.getMetersPerPixelAtLatitude(any(Double.class))).thenReturn(Double.valueOf(1000));
LocationCameraController camera = buildCamera(mapboxMap);
camera.initializeOptions(mock(LocationComponentOptions.class));
final OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class);
@@ -594,6 +644,9 @@ public class LocationCameraControllerTest {
public void transition_mapboxAnimateBearing() {
MapboxMap mapboxMap = mock(MapboxMap.class);
when(mapboxMap.getCameraPosition()).thenReturn(CameraPosition.DEFAULT);
Projection projection = mock(Projection.class);
when(mapboxMap.getProjection()).thenReturn(projection);
when(projection.getMetersPerPixelAtLatitude(any(Double.class))).thenReturn(Double.valueOf(1000));
LocationCameraController camera = buildCamera(mapboxMap);
camera.initializeOptions(mock(LocationComponentOptions.class));
final OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class);
@@ -616,6 +669,9 @@ public class LocationCameraControllerTest {
public void transition_mapboxAnimateNorth() {
MapboxMap mapboxMap = mock(MapboxMap.class);
when(mapboxMap.getCameraPosition()).thenReturn(CameraPosition.DEFAULT);
Projection projection = mock(Projection.class);
when(mapboxMap.getProjection()).thenReturn(projection);
when(projection.getMetersPerPixelAtLatitude(any(Double.class))).thenReturn(Double.valueOf(1000));
LocationCameraController camera = buildCamera(mapboxMap);
camera.initializeOptions(mock(LocationComponentOptions.class));
final OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class);
@@ -638,6 +694,9 @@ public class LocationCameraControllerTest {
public void transition_animatorValuesDuringTransition() {
MapboxMap mapboxMap = mock(MapboxMap.class);
when(mapboxMap.getCameraPosition()).thenReturn(CameraPosition.DEFAULT);
Projection projection = mock(Projection.class);
when(mapboxMap.getProjection()).thenReturn(projection);
when(projection.getMetersPerPixelAtLatitude(any(Double.class))).thenReturn(Double.valueOf(1000));
LocationCameraController camera = buildCamera(mapboxMap);
camera.initializeOptions(mock(LocationComponentOptions.class));
final OnLocationCameraTransitionListener listener = mock(OnLocationCameraTransitionListener.class);
@@ -674,6 +733,9 @@ public class LocationCameraControllerTest {
private LocationCameraController buildCamera(OnCameraTrackingChangedListener onCameraTrackingChangedListener) {
MapboxMap mapboxMap = mock(MapboxMap.class);
when(mapboxMap.getUiSettings()).thenReturn(mock(UiSettings.class));
Projection projection = mock(Projection.class);
when(mapboxMap.getProjection()).thenReturn(projection);
when(projection.getMetersPerPixelAtLatitude(any(Double.class))).thenReturn(Double.valueOf(1000));
MoveGestureDetector moveGestureDetector = mock(MoveGestureDetector.class);
OnCameraMoveInvalidateListener onCameraMoveInvalidateListener = mock(OnCameraMoveInvalidateListener.class);
AndroidGesturesManager initialGesturesManager = mock(AndroidGesturesManager.class);
@@ -685,6 +747,9 @@ public class LocationCameraControllerTest {
private LocationCameraController buildCamera(MoveGestureDetector moveGestureDetector) {
MapboxMap mapboxMap = mock(MapboxMap.class);
when(mapboxMap.getUiSettings()).thenReturn(mock(UiSettings.class));
Projection projection = mock(Projection.class);
when(mapboxMap.getProjection()).thenReturn(projection);
when(projection.getMetersPerPixelAtLatitude(any(Double.class))).thenReturn(Double.valueOf(1000));
OnCameraTrackingChangedListener onCameraTrackingChangedListener = mock(OnCameraTrackingChangedListener.class);
OnCameraMoveInvalidateListener onCameraMoveInvalidateListener = mock(OnCameraMoveInvalidateListener.class);
AndroidGesturesManager initialGesturesManager = mock(AndroidGesturesManager.class);

View File

@@ -1165,12 +1165,15 @@ class LocationComponentTest : BaseActivityTest() {
uiController: UiController, context: Context) {
component.activateLocationComponent(context, mapboxMap.style!!, false)
component.isLocationComponentEnabled = true
val target = LatLng(51.0, 17.0)
assertTrue(target.distanceTo(LatLng(location)) > LocationComponentConstants.INSTANT_LOCATION_TRANSITION_THRESHOLD)
component.cameraMode = CameraMode.NONE
component.forceLocationUpdate(location)
val target = LatLng(51.0, 17.0)
mapboxMap.moveCamera(CameraUpdateFactory.newLatLng(target))
mapboxMap.moveCamera(CameraUpdateFactory.zoomTo(15.0))
mapboxMap.moveCamera(CameraUpdateFactory.bearingTo(90.0))
assertTrue(Utils.immediateAnimation(mapboxMap.projection, mapboxMap.cameraPosition.target, LatLng(location)))
component.cameraMode = CameraMode.TRACKING_GPS
assertEquals(location.bearing.toDouble(), mapboxMap.cameraPosition.bearing, 0.1)
assertEquals(location.latitude, mapboxMap.cameraPosition.target.latitude, 0.1)