Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

CameraMode transition animation and result listeners #13523

Merged
merged 1 commit into from
Dec 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

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;
Expand All @@ -31,6 +30,7 @@
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 {

Expand Down Expand Up @@ -381,13 +381,4 @@ private void cancelAnimator(@MapboxAnimator.Type int animatorType) {
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

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;
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;
Expand All @@ -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,
Expand Down Expand Up @@ -76,33 +81,112 @@ void initializeOptions(LocationComponentOptions options) {
}

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);
}
}
LukasPaczos marked this conversation as resolved.
Show resolved Hide resolved
}

int getCameraMode() {
return cameraMode;
}

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();
}
Expand All @@ -114,12 +198,6 @@ public void onNewLatLngValue(@NonNull LatLng latLng) {
|| 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;
}
}
}

Expand Down Expand Up @@ -153,6 +231,10 @@ public void onNewTiltValue(float tilt) {
setTilt(tilt);
}

boolean isTransitioning() {
return isTransitioning;
}

private void adjustGesturesThresholds() {
if (options.trackingGesturesManagement()) {
if (isLocationTracking()) {
Expand All @@ -179,6 +261,11 @@ private boolean isBearingTracking() {
|| 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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,10 @@ public boolean isLocationComponentEnabled() {
/**
* 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>
Expand All @@ -409,9 +413,65 @@ public boolean isLocationComponentEnabled() {
* @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);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
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() {
Expand Down Expand Up @@ -89,6 +92,15 @@ static float calculateZoomLevelRadius(@NonNull MapboxMap mapboxMap, @Nullable Lo
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.
*/
Expand Down
Loading