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

Commit

Permalink
[android] CameraMode transition animation and result listeners
Browse files Browse the repository at this point in the history
  • Loading branch information
LukasPaczos committed Dec 10, 2018
1 parent ebd87a5 commit b4d9266
Show file tree
Hide file tree
Showing 8 changed files with 523 additions and 40 deletions.
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);
}
}
}

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

0 comments on commit b4d9266

Please sign in to comment.