Adding pulsing circle to LocationComponent options (#172)

* [location] added pulsing locationcomponent circle

* adjusting style switching to roll through all styles

* refactored methods and variables for prefix continuity

* refactoring to after making start and stop private methods

* refactoring to stop pulsing when LocationComponent is disabled and color/speed is then selected

* refactoring to swap interpolator string paramete for interpolator interface parameter

* LocationLayerControllerTest adjustments

* removing unneeded test

* more rebase adjustments

* [location] added pulsing locationcomponent circle

* refactoring to swap interpolator string paramete for interpolator interface parameter

* consolidating pulse ui update methods

* gl-native vendor bump

* refactor to check for activation with locationcomponent

* Update MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java

Co-Authored-By: Łukasz Paczos <lukasz.paczos@mapbox.com>

* Update MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java

Co-Authored-By: Łukasz Paczos <lukasz.paczos@mapbox.com>

* Update MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java

Co-Authored-By: Łukasz Paczos <lukasz.paczos@mapbox.com>

* Update MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java

Co-Authored-By: Łukasz Paczos <lukasz.paczos@mapbox.com>

* Update MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java

Co-Authored-By: Łukasz Paczos <lukasz.paczos@mapbox.com>

* Added incompatibility docs

* Added pulse alpha range explanation

* Do not wrap pulsing circle animator with an animator set to avoid leaks

Co-authored-by: Łukasz Paczos <lukasz.paczos@mapbox.com>
This commit is contained in:
Langston Smith
2020-04-30 03:28:36 -07:00
committed by GitHub
parent 6fd498ed16
commit 9bfe77df40
32 changed files with 1772 additions and 213 deletions

View File

@@ -2,8 +2,6 @@ package com.mapbox.mapboxsdk.location;
import android.graphics.Bitmap;
import androidx.annotation.Nullable;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.location.modes.RenderMode;
import com.mapbox.mapboxsdk.maps.Style;
@@ -12,6 +10,8 @@ import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.utils.BitmapUtils;
import com.mapbox.mapboxsdk.utils.ColorUtils;
import androidx.annotation.Nullable;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.BACKGROUND_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.BACKGROUND_STALE_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.BEARING_ICON;
@@ -181,6 +181,30 @@ class IndicatorLocationLayerRenderer implements LocationLayerRenderer {
lastBearing = bearing;
}
/**
* Adjust the visibility of the pulsing LocationComponent circle.
*/
@Override
public void adjustPulsingCircleLayerVisibility(boolean visible) {
// not supported at this time
}
/**
* Adjust the the pulsing LocationComponent circle based on the set options.
*/
@Override
public void stylePulsingCircle(LocationComponentOptions options) {
// not supported at this time
}
/**
* Adjust the visual appearance of the pulsing LocationComponent circle.
*/
@Override
public void updatePulsingUi(float radius, @Nullable Float opacity) {
// not supported at this time
}
private void setImages(@RenderMode.Mode int renderMode, boolean isStale) {
String topImage = "";
String bearingImage = "";

View File

@@ -32,6 +32,7 @@ import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_GPS_BEARING;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_LOCATION_STALE;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_SHADOW_ICON_OFFSET;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PULSING_CIRCLE_LAYER;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.SHADOW_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.SHADOW_LAYER;
import static com.mapbox.mapboxsdk.style.expressions.Expression.get;
@@ -131,9 +132,23 @@ class LayerSourceProvider {
LocationIndicatorLayer layer = new LocationIndicatorLayer(FOREGROUND_LAYER);
layer.setLocationTransition(new TransitionOptions(0, 0));
layer.setProperties(
LocationPropertyFactory.perspectiveCompensation(0.9f),
LocationPropertyFactory.imageTiltDisplacement(4f)
LocationPropertyFactory.perspectiveCompensation(0.9f),
LocationPropertyFactory.imageTiltDisplacement(4f)
);
return layer;
}
/**
* Adds a {@link CircleLayer} to the map to support the {@link LocationComponent} pulsing UI functionality.
*
* @return a {@link CircleLayer} with the correct data-driven styling. Tilting the map will keep the pulsing
* layer aligned with the map plane.
*/
@NonNull
Layer generatePulsingCircleLayer() {
return new CircleLayer(PULSING_CIRCLE_LAYER, LOCATION_SOURCE)
.withProperties(
circlePitchAlignment(Property.CIRCLE_PITCH_ALIGNMENT_MAP)
);
}
}

View File

@@ -4,6 +4,7 @@ import android.animation.Animator;
import android.location.Location;
import android.os.SystemClock;
import android.util.SparseArray;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
import androidx.annotation.NonNull;
@@ -32,6 +33,7 @@ import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_LAYER_ACCURA
import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_LAYER_COMPASS_BEARING;
import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_LAYER_GPS_BEARING;
import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_LAYER_LATLNG;
import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_PULSING_CIRCLE;
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;
@@ -186,6 +188,28 @@ final class LocationAnimatorCoordinator {
this.previousAccuracyRadius = targetAccuracyRadius;
}
/**
* Initializes the {@link PulsingLocationCircleAnimator}, which is a type of {@link MapboxAnimator}.
* This method also adds the animator to this class' animator array.
*
* @param options the {@link LocationComponentOptions} passed to this class upstream from the
* {@link LocationComponent}.
*/
void startLocationComponentCirclePulsing(LocationComponentOptions options) {
cancelAnimator(ANIMATOR_PULSING_CIRCLE);
MapboxAnimator.AnimationsValueChangeListener listener = listeners.get(ANIMATOR_PULSING_CIRCLE);
if (listener != null) {
PulsingLocationCircleAnimator pulsingLocationCircleAnimator = animatorProvider.pulsingCircleAnimator(
listener,
maxAnimationFps,
options.pulseSingleDuration(),
options.pulseMaxRadius(),
options.pulseInterpolator() == null ? new DecelerateInterpolator() : options.pulseInterpolator());
animatorArray.put(ANIMATOR_PULSING_CIRCLE, pulsingLocationCircleAnimator);
playPulsingAnimator();
}
}
void feedNewZoomLevel(double targetZoomLevel, @NonNull CameraPosition currentCameraPosition, long animationDuration,
@Nullable MapboxMap.CancelableCallback callback) {
updateZoomAnimator((float) targetZoomLevel, (float) currentCameraPosition.zoom, callback);
@@ -349,6 +373,18 @@ final class LocationAnimatorCoordinator {
animatorSetProvider.startAnimation(animators, new LinearInterpolator(), duration);
}
/**
* Starts the {@link PulsingLocationCircleAnimator} in the animator array. This method is separate
* from {@link #playAnimators(long, int...)} because the MapboxAnimatorSetProvider has many more
* customizable animation parameters than the other {@link MapboxAnimator}s.
*/
private void playPulsingAnimator() {
Animator animator = animatorArray.get(ANIMATOR_PULSING_CIRCLE);
if (animator != null) {
animator.start();
}
}
void resetAllCameraAnimations(@NonNull CameraPosition currentCameraPosition, boolean isGpsNorth) {
resetCameraCompassAnimation(currentCameraPosition);
boolean snap = resetCameraLocationAnimations(currentCameraPosition, isGpsNorth);
@@ -446,6 +482,13 @@ final class LocationAnimatorCoordinator {
cancelAnimator(ANIMATOR_TILT);
}
/**
* Cancel the pulsing circle location animator.
*/
void stopPulsingCircleAnimation() {
cancelAnimator(ANIMATOR_PULSING_CIRCLE);
}
void cancelAllAnimations() {
for (int i = 0; i < animatorArray.size(); i++) {
@MapboxAnimator.Type int animatorType = animatorArray.keyAt(i);

View File

@@ -726,10 +726,25 @@ public final class LocationComponent {
locationAnimatorCoordinator.setTrackingAnimationDurationMultiplier(options.trackingAnimationDurationMultiplier());
locationAnimatorCoordinator.setCompassAnimationEnabled(options.compassAnimationEnabled());
locationAnimatorCoordinator.setAccuracyAnimationEnabled(options.accuracyAnimationEnabled());
if (options.pulseEnabled()) {
startPulsingLocationCircle();
} else {
stopPulsingLocationCircle();
}
updateMapWithOptions(options);
}
}
/**
* Starts the LocationComponent's pulsing circle UI.
*/
private void startPulsingLocationCircle() {
if (isEnabled && isLayerReady) {
locationAnimatorCoordinator.startLocationComponentCirclePulsing(options);
locationLayerController.adjustPulsingCircleLayerVisibility(true);
}
}
/**
* Zooms to the desired zoom level.
* This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
@@ -1215,6 +1230,14 @@ public final class LocationComponent {
}
}
/**
* Stop the LocationComponent's pulsing circle animation.
*/
private void stopPulsingLocationCircle() {
locationAnimatorCoordinator.stopPulsingCircleAnimation();
locationLayerController.adjustPulsingCircleLayerVisibility(false);
}
@SuppressLint("MissingPermission")
private void onLocationLayerStart() {
if (!isComponentInitialized || !isComponentStarted || mapboxMap.getStyle() == null) {
@@ -1240,6 +1263,11 @@ public final class LocationComponent {
}
}
setCameraMode(locationCameraController.getCameraMode());
if (options.pulseEnabled()) {
startPulsingLocationCircle();
} else {
stopPulsingLocationCircle();
}
setLastLocation();
updateCompassListenerState(true);
setLastCompassHeading();
@@ -1256,6 +1284,8 @@ public final class LocationComponent {
if (compassEngine != null) {
updateCompassListenerState(false);
}
stopPulsingLocationCircle();
locationAnimatorCoordinator.cancelAllAnimations();
if (locationEngine != null) {
locationEngine.removeLocationUpdates(currentLocationEngineListener);
@@ -1430,6 +1460,9 @@ public final class LocationComponent {
boolean isLocationLayerHidden = locationLayerController.isHidden();
if (isEnabled && isComponentStarted && isLocationLayerHidden) {
locationLayerController.show();
if (options.pulseEnabled()) {
locationLayerController.adjustPulsingCircleLayerVisibility(true);
}
}
}

View File

@@ -131,6 +131,10 @@ public class LocationComponentActivationOptions {
* or {@link LocationComponentConstants#LOCATION_SOURCE} are ignored.
* <li> All options that alter the image ID, like {@link LocationComponentOptions#foregroundName()}, are ignored.
* Use {@link LocationComponentOptions#foregroundDrawable()} to alter the image rendered as the puck.
* <li> The LocationComponent's pulsing effect. Any of the {@link LocationComponentOptions}'
* pulsing methods such as {@link LocationComponentOptions#pulseEnabled()} or
* {@link LocationComponentOptions#pulseColor()}, are useless when using the
* the specialized location layer.
* </ul>
*
* @return whether the default specialized location layer is used
@@ -244,6 +248,10 @@ public class LocationComponentActivationOptions {
* or {@link LocationComponentConstants#LOCATION_SOURCE} are ignored.
* <li> All options that alter the image ID, like {@link LocationComponentOptions#foregroundName()}, are ignored.
* Use {@link LocationComponentOptions#foregroundDrawable()} to alter the image rendered as the puck.
* <li> The LocationComponent's pulsing effect. Any of the {@link LocationComponentOptions}'
* pulsing methods such as {@link LocationComponentOptions#pulseEnabled()} or
* {@link LocationComponentOptions#pulseColor()}, are useless when using the
* the specialized location layer.
* </ul>
*
* @param useSpecializedLocationLayer true if you want to initialize and use the

View File

@@ -52,6 +52,8 @@ public final class LocationComponentConstants {
static final String PROPERTY_FOREGROUND_STALE_ICON = "mapbox-property-foreground-stale-icon";
static final String PROPERTY_BACKGROUND_STALE_ICON = "mapbox-property-background-stale-icon";
static final String PROPERTY_BEARING_ICON = "mapbox-property-shadow-icon";
static final String PROPERTY_PULSING_RADIUS = "mapbox-property-pulsing-circle-radius";
static final String PROPERTY_PULSING_OPACITY = "mapbox-property-pulsing-circle-opacity";
// Layers
@@ -80,6 +82,11 @@ public final class LocationComponentConstants {
*/
public static final String BEARING_LAYER = "mapbox-location-bearing-layer";
/**
* Layer ID of the location pulsing circle.
*/
public static final String PULSING_CIRCLE_LAYER = "mapbox-location-pulsing-circle-layer";
// Icons
static final String FOREGROUND_ICON = "mapbox-location-icon";
static final String BACKGROUND_ICON = "mapbox-location-stroke-icon";

View File

@@ -5,12 +5,7 @@ import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.ColorInt;
import androidx.annotation.Dimension;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import android.view.animation.Interpolator;
import com.mapbox.android.gestures.AndroidGesturesManager;
import com.mapbox.mapboxsdk.R;
@@ -18,6 +13,13 @@ import com.mapbox.mapboxsdk.style.layers.Layer;
import java.util.Arrays;
import androidx.annotation.ColorInt;
import androidx.annotation.Dimension;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
/**
* This class exposes options for the Location Component. The options can be set by defining a
* style in your apps style.xml file and passing in directly into the {@link LocationComponent}
@@ -69,6 +71,21 @@ public class LocationComponentOptions implements Parcelable {
*/
private static final float TRACKING_ANIMATION_DURATION_MULTIPLIER_DEFAULT = 1.1f;
/**
* Default duration of a single LocationComponent circle pulse.
*/
private static final long CIRCLE_PULSING_DURATION_DEFAULT_MS = 2300;
/**
* Default opacity of the LocationComponent circle when it ends a single pulse.
*/
private static final float CIRCLE_PULSING_ALPHA_DEFAULT = 1f;
/**
* Default maximum radius of the LocationComponent circle when it's pulsing.
*/
public static final float CIRCLE_PULSING_MAX_RADIUS_DEFAULT = 35f;
private float accuracyAlpha;
private int accuracyColor;
private int backgroundDrawableStale;
@@ -114,41 +131,56 @@ public class LocationComponentOptions implements Parcelable {
private float trackingAnimationDurationMultiplier;
private boolean compassAnimationEnabled;
private boolean accuracyAnimationEnabled;
private Boolean pulseEnabled;
private Boolean pulseFadeEnabled;
private Integer pulseColor;
private float pulseSingleDuration;
private float pulseMaxRadius;
private float pulseAlpha;
@Nullable
private Interpolator pulseInterpolator;
public LocationComponentOptions(
float accuracyAlpha,
int accuracyColor,
int backgroundDrawableStale,
@Nullable String backgroundStaleName,
int foregroundDrawableStale,
@Nullable String foregroundStaleName,
int gpsDrawable,
@Nullable String gpsName,
int foregroundDrawable,
@Nullable String foregroundName,
int backgroundDrawable,
@Nullable String backgroundName,
int bearingDrawable,
@Nullable String bearingName,
@Nullable Integer bearingTintColor,
@Nullable Integer foregroundTintColor,
@Nullable Integer backgroundTintColor,
@Nullable Integer foregroundStaleTintColor,
@Nullable Integer backgroundStaleTintColor,
float elevation,
boolean enableStaleState,
long staleStateTimeout,
@Nullable int[] padding,
float maxZoomIconScale,
float minZoomIconScale,
boolean trackingGesturesManagement,
float trackingInitialMoveThreshold,
float trackingMultiFingerMoveThreshold,
String layerAbove,
String layerBelow,
float trackingAnimationDurationMultiplier,
boolean compassAnimationEnabled,
boolean accuracyAnimationEnabled) {
float accuracyAlpha,
int accuracyColor,
int backgroundDrawableStale,
@Nullable String backgroundStaleName,
int foregroundDrawableStale,
@Nullable String foregroundStaleName,
int gpsDrawable,
@Nullable String gpsName,
int foregroundDrawable,
@Nullable String foregroundName,
int backgroundDrawable,
@Nullable String backgroundName,
int bearingDrawable,
@Nullable String bearingName,
@Nullable Integer bearingTintColor,
@Nullable Integer foregroundTintColor,
@Nullable Integer backgroundTintColor,
@Nullable Integer foregroundStaleTintColor,
@Nullable Integer backgroundStaleTintColor,
float elevation,
boolean enableStaleState,
long staleStateTimeout,
@Nullable int[] padding,
float maxZoomIconScale,
float minZoomIconScale,
boolean trackingGesturesManagement,
float trackingInitialMoveThreshold,
float trackingMultiFingerMoveThreshold,
String layerAbove,
String layerBelow,
float trackingAnimationDurationMultiplier,
boolean compassAnimationEnabled,
boolean accuracyAnimationEnabled,
Boolean pulseEnabled,
Boolean pulseFadeEnabled,
Integer pulseColor,
float pulseSingleDuration,
float pulseMaxRadius,
float pulseAlpha,
@Nullable Interpolator pulseInterpolator) {
this.accuracyAlpha = accuracyAlpha;
this.accuracyColor = accuracyColor;
this.backgroundDrawableStale = backgroundDrawableStale;
@@ -185,6 +217,13 @@ public class LocationComponentOptions implements Parcelable {
this.trackingAnimationDurationMultiplier = trackingAnimationDurationMultiplier;
this.compassAnimationEnabled = compassAnimationEnabled;
this.accuracyAnimationEnabled = accuracyAnimationEnabled;
this.pulseEnabled = pulseEnabled;
this.pulseFadeEnabled = pulseFadeEnabled;
this.pulseColor = pulseColor;
this.pulseSingleDuration = pulseSingleDuration;
this.pulseMaxRadius = pulseMaxRadius;
this.pulseAlpha = pulseAlpha;
this.pulseInterpolator = pulseInterpolator;
}
/**
@@ -202,106 +241,131 @@ public class LocationComponentOptions implements Parcelable {
@StyleRes int styleRes) {
TypedArray typedArray = context.obtainStyledAttributes(
styleRes, R.styleable.mapbox_LocationComponent);
styleRes, R.styleable.mapbox_LocationComponent);
LocationComponentOptions.Builder builder = new LocationComponentOptions.Builder()
.enableStaleState(true)
.staleStateTimeout(STALE_STATE_DELAY_MS)
.maxZoomIconScale(MAX_ZOOM_ICON_SCALE_DEFAULT)
.minZoomIconScale(MIN_ZOOM_ICON_SCALE_DEFAULT)
.padding(PADDING_DEFAULT);
.enableStaleState(true)
.staleStateTimeout(STALE_STATE_DELAY_MS)
.maxZoomIconScale(MAX_ZOOM_ICON_SCALE_DEFAULT)
.minZoomIconScale(MIN_ZOOM_ICON_SCALE_DEFAULT)
.padding(PADDING_DEFAULT);
builder.foregroundDrawable(typedArray.getResourceId(
R.styleable.mapbox_LocationComponent_mapbox_foregroundDrawable, -1));
R.styleable.mapbox_LocationComponent_mapbox_foregroundDrawable, -1));
if (typedArray.hasValue(R.styleable.mapbox_LocationComponent_mapbox_foregroundTintColor)) {
builder.foregroundTintColor(typedArray.getColor(
R.styleable.mapbox_LocationComponent_mapbox_foregroundTintColor, -1));
R.styleable.mapbox_LocationComponent_mapbox_foregroundTintColor, -1));
}
builder.backgroundDrawable(typedArray.getResourceId(
R.styleable.mapbox_LocationComponent_mapbox_backgroundDrawable, -1));
R.styleable.mapbox_LocationComponent_mapbox_backgroundDrawable, -1));
if (typedArray.hasValue(R.styleable.mapbox_LocationComponent_mapbox_backgroundTintColor)) {
builder.backgroundTintColor(typedArray.getColor(
R.styleable.mapbox_LocationComponent_mapbox_backgroundTintColor, -1));
R.styleable.mapbox_LocationComponent_mapbox_backgroundTintColor, -1));
}
builder.foregroundDrawableStale(typedArray.getResourceId(
R.styleable.mapbox_LocationComponent_mapbox_foregroundDrawableStale, -1));
R.styleable.mapbox_LocationComponent_mapbox_foregroundDrawableStale, -1));
if (typedArray.hasValue(R.styleable.mapbox_LocationComponent_mapbox_foregroundStaleTintColor)) {
builder.foregroundStaleTintColor(typedArray.getColor(
R.styleable.mapbox_LocationComponent_mapbox_foregroundStaleTintColor, -1));
R.styleable.mapbox_LocationComponent_mapbox_foregroundStaleTintColor, -1));
}
builder.backgroundDrawableStale(typedArray.getResourceId(
R.styleable.mapbox_LocationComponent_mapbox_backgroundDrawableStale, -1));
R.styleable.mapbox_LocationComponent_mapbox_backgroundDrawableStale, -1));
if (typedArray.hasValue(R.styleable.mapbox_LocationComponent_mapbox_backgroundStaleTintColor)) {
builder.backgroundStaleTintColor(typedArray.getColor(
R.styleable.mapbox_LocationComponent_mapbox_backgroundStaleTintColor, -1));
R.styleable.mapbox_LocationComponent_mapbox_backgroundStaleTintColor, -1));
}
builder.bearingDrawable(typedArray.getResourceId(
R.styleable.mapbox_LocationComponent_mapbox_bearingDrawable, -1));
R.styleable.mapbox_LocationComponent_mapbox_bearingDrawable, -1));
if (typedArray.hasValue(R.styleable.mapbox_LocationComponent_mapbox_bearingTintColor)) {
builder.bearingTintColor(typedArray.getColor(
R.styleable.mapbox_LocationComponent_mapbox_bearingTintColor, -1));
R.styleable.mapbox_LocationComponent_mapbox_bearingTintColor, -1));
}
if (typedArray.hasValue(R.styleable.mapbox_LocationComponent_mapbox_enableStaleState)) {
builder.enableStaleState(typedArray.getBoolean(
R.styleable.mapbox_LocationComponent_mapbox_enableStaleState, true));
R.styleable.mapbox_LocationComponent_mapbox_enableStaleState, true));
}
if (typedArray.hasValue(R.styleable.mapbox_LocationComponent_mapbox_staleStateTimeout)) {
builder.staleStateTimeout(typedArray.getInteger(
R.styleable.mapbox_LocationComponent_mapbox_staleStateTimeout, (int) STALE_STATE_DELAY_MS));
R.styleable.mapbox_LocationComponent_mapbox_staleStateTimeout, (int) STALE_STATE_DELAY_MS));
}
builder.gpsDrawable(typedArray.getResourceId(
R.styleable.mapbox_LocationComponent_mapbox_gpsDrawable, -1));
R.styleable.mapbox_LocationComponent_mapbox_gpsDrawable, -1));
float elevation = typedArray.getDimension(
R.styleable.mapbox_LocationComponent_mapbox_elevation, 0);
R.styleable.mapbox_LocationComponent_mapbox_elevation, 0);
builder.accuracyColor(typedArray.getColor(
R.styleable.mapbox_LocationComponent_mapbox_accuracyColor, -1));
R.styleable.mapbox_LocationComponent_mapbox_accuracyColor, -1));
builder.accuracyAlpha(typedArray.getFloat(
R.styleable.mapbox_LocationComponent_mapbox_accuracyAlpha, ACCURACY_ALPHA_DEFAULT));
R.styleable.mapbox_LocationComponent_mapbox_accuracyAlpha, ACCURACY_ALPHA_DEFAULT));
builder.elevation(elevation);
builder.trackingGesturesManagement(typedArray.getBoolean(
R.styleable.mapbox_LocationComponent_mapbox_trackingGesturesManagement, false));
R.styleable.mapbox_LocationComponent_mapbox_trackingGesturesManagement, false));
builder.trackingInitialMoveThreshold(typedArray.getDimension(
R.styleable.mapbox_LocationComponent_mapbox_trackingInitialMoveThreshold,
context.getResources().getDimension(R.dimen.mapbox_locationComponentTrackingInitialMoveThreshold)));
R.styleable.mapbox_LocationComponent_mapbox_trackingInitialMoveThreshold,
context.getResources().getDimension(R.dimen.mapbox_locationComponentTrackingInitialMoveThreshold)));
builder.trackingMultiFingerMoveThreshold(typedArray.getDimension(
R.styleable.mapbox_LocationComponent_mapbox_trackingMultiFingerMoveThreshold,
context.getResources().getDimension(R.dimen.mapbox_locationComponentTrackingMultiFingerMoveThreshold)));
R.styleable.mapbox_LocationComponent_mapbox_trackingMultiFingerMoveThreshold,
context.getResources().getDimension(R.dimen.mapbox_locationComponentTrackingMultiFingerMoveThreshold)));
builder.padding(new int[] {
typedArray.getInt(R.styleable.mapbox_LocationComponent_mapbox_iconPaddingLeft, 0),
typedArray.getInt(R.styleable.mapbox_LocationComponent_mapbox_iconPaddingTop, 0),
typedArray.getInt(R.styleable.mapbox_LocationComponent_mapbox_iconPaddingRight, 0),
typedArray.getInt(R.styleable.mapbox_LocationComponent_mapbox_iconPaddingBottom, 0),
builder.padding(new int[]{
typedArray.getInt(R.styleable.mapbox_LocationComponent_mapbox_iconPaddingLeft, 0),
typedArray.getInt(R.styleable.mapbox_LocationComponent_mapbox_iconPaddingTop, 0),
typedArray.getInt(R.styleable.mapbox_LocationComponent_mapbox_iconPaddingRight, 0),
typedArray.getInt(R.styleable.mapbox_LocationComponent_mapbox_iconPaddingBottom, 0),
});
builder.layerAbove(
typedArray.getString(R.styleable.mapbox_LocationComponent_mapbox_layer_above));
typedArray.getString(R.styleable.mapbox_LocationComponent_mapbox_layer_above));
builder.layerBelow(
typedArray.getString(R.styleable.mapbox_LocationComponent_mapbox_layer_below));
typedArray.getString(R.styleable.mapbox_LocationComponent_mapbox_layer_below));
float minScale = typedArray.getFloat(
R.styleable.mapbox_LocationComponent_mapbox_minZoomIconScale, MIN_ZOOM_ICON_SCALE_DEFAULT);
R.styleable.mapbox_LocationComponent_mapbox_minZoomIconScale, MIN_ZOOM_ICON_SCALE_DEFAULT);
float maxScale = typedArray.getFloat(
R.styleable.mapbox_LocationComponent_mapbox_maxZoomIconScale, MAX_ZOOM_ICON_SCALE_DEFAULT);
R.styleable.mapbox_LocationComponent_mapbox_maxZoomIconScale, MAX_ZOOM_ICON_SCALE_DEFAULT);
builder.minZoomIconScale(minScale);
builder.maxZoomIconScale(maxScale);
float trackingAnimationDurationMultiplier = typedArray.getFloat(
R.styleable.mapbox_LocationComponent_mapbox_trackingAnimationDurationMultiplier,
TRACKING_ANIMATION_DURATION_MULTIPLIER_DEFAULT
R.styleable.mapbox_LocationComponent_mapbox_trackingAnimationDurationMultiplier,
TRACKING_ANIMATION_DURATION_MULTIPLIER_DEFAULT
);
builder.trackingAnimationDurationMultiplier(trackingAnimationDurationMultiplier);
builder.compassAnimationEnabled = typedArray.getBoolean(
R.styleable.mapbox_LocationComponent_mapbox_compassAnimationEnabled, true
R.styleable.mapbox_LocationComponent_mapbox_compassAnimationEnabled, true
);
builder.accuracyAnimationEnabled = typedArray.getBoolean(
R.styleable.mapbox_LocationComponent_mapbox_accuracyAnimationEnabled, true
R.styleable.mapbox_LocationComponent_mapbox_accuracyAnimationEnabled, true
);
builder.pulseEnabled = typedArray.getBoolean(
R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleEnabled, false
);
builder.pulseFadeEnabled = typedArray.getBoolean(
R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleFadeEnabled, true
);
if (typedArray.hasValue(R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleColor)) {
builder.pulseColor(typedArray.getColor(
R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleColor,
-1));
}
builder.pulseSingleDuration = typedArray.getFloat(
R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleDuration, CIRCLE_PULSING_DURATION_DEFAULT_MS
);
builder.pulseMaxRadius = typedArray.getFloat(
R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleRadius, CIRCLE_PULSING_MAX_RADIUS_DEFAULT
);
builder.pulseAlpha = typedArray.getFloat(
R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleAlpha, CIRCLE_PULSING_ALPHA_DEFAULT);
typedArray.recycle();
return builder.build();
@@ -330,7 +394,7 @@ public class LocationComponentOptions implements Parcelable {
@NonNull
public static Builder builder(@NonNull Context context) {
return LocationComponentOptions.createFromAttributes(context,
R.style.mapbox_LocationComponent).toBuilder();
R.style.mapbox_LocationComponent).toBuilder();
}
/**
@@ -740,42 +804,114 @@ public class LocationComponentOptions implements Parcelable {
return accuracyAnimationEnabled;
}
/**
* Enable or disable the LocationComponent's pulsing circle.
*
* @return whether the LocationComponent's pulsing circle is enabled
*/
public Boolean pulseEnabled() {
return pulseEnabled;
}
/**
* Enable or disable fading of the LocationComponent's pulsing circle. If it fades, the circle's
* opacity decreases as its radius increases.
*
* @return whether fading of the LocationComponent's pulsing circle is enabled
*/
public Boolean pulseFadeEnabled() {
return pulseFadeEnabled;
}
/**
* Color of the LocationComponent's pulsing circle as it pulses.
*
* @return the current set color of the circle
*/
public Integer pulseColor() {
return pulseColor;
}
/**
* The number of milliseconds it takes for a single pulse of the LocationComponent's pulsing circle.
*
* @return the current set length of time for a single pulse
*/
public float pulseSingleDuration() {
return pulseSingleDuration;
}
/**
* The maximum radius that a single pulse should expand the LocationComponent's pulsing circle to.
*
* @return the maximum radius that the pulsing circle will expand to.
*/
public float pulseMaxRadius() {
return pulseMaxRadius;
}
/**
* The opacity of the LocationComponent's circle as it pulses. The expected range is
* 0 to 1. An opacity of 1 makes the layer fully visible.
*
* @return the current opacity of the LocationComponent's pulsing circle
*/
public float pulseAlpha() {
return pulseAlpha;
}
/**
* The interpolator type of animation for the movement of the LocationComponent's circle
*
* @return the current set type of animation interpolator for the pulsing circle
*/
@Nullable
public Interpolator pulseInterpolator() {
return pulseInterpolator;
}
@NonNull
@Override
public String toString() {
return "LocationComponentOptions{"
+ "accuracyAlpha=" + accuracyAlpha + ", "
+ "accuracyColor=" + accuracyColor + ", "
+ "backgroundDrawableStale=" + backgroundDrawableStale + ", "
+ "backgroundStaleName=" + backgroundStaleName + ", "
+ "foregroundDrawableStale=" + foregroundDrawableStale + ", "
+ "foregroundStaleName=" + foregroundStaleName + ", "
+ "gpsDrawable=" + gpsDrawable + ", "
+ "gpsName=" + gpsName + ", "
+ "foregroundDrawable=" + foregroundDrawable + ", "
+ "foregroundName=" + foregroundName + ", "
+ "backgroundDrawable=" + backgroundDrawable + ", "
+ "backgroundName=" + backgroundName + ", "
+ "bearingDrawable=" + bearingDrawable + ", "
+ "bearingName=" + bearingName + ", "
+ "bearingTintColor=" + bearingTintColor + ", "
+ "foregroundTintColor=" + foregroundTintColor + ", "
+ "backgroundTintColor=" + backgroundTintColor + ", "
+ "foregroundStaleTintColor=" + foregroundStaleTintColor + ", "
+ "backgroundStaleTintColor=" + backgroundStaleTintColor + ", "
+ "elevation=" + elevation + ", "
+ "enableStaleState=" + enableStaleState + ", "
+ "staleStateTimeout=" + staleStateTimeout + ", "
+ "padding=" + Arrays.toString(padding) + ", "
+ "maxZoomIconScale=" + maxZoomIconScale + ", "
+ "minZoomIconScale=" + minZoomIconScale + ", "
+ "trackingGesturesManagement=" + trackingGesturesManagement + ", "
+ "trackingInitialMoveThreshold=" + trackingInitialMoveThreshold + ", "
+ "trackingMultiFingerMoveThreshold=" + trackingMultiFingerMoveThreshold + ", "
+ "layerAbove=" + layerAbove
+ "layerBelow=" + layerBelow
+ "trackingAnimationDurationMultiplier=" + trackingAnimationDurationMultiplier
+ "}";
+ "accuracyAlpha=" + accuracyAlpha + ", "
+ "accuracyColor=" + accuracyColor + ", "
+ "backgroundDrawableStale=" + backgroundDrawableStale + ", "
+ "backgroundStaleName=" + backgroundStaleName + ", "
+ "foregroundDrawableStale=" + foregroundDrawableStale + ", "
+ "foregroundStaleName=" + foregroundStaleName + ", "
+ "gpsDrawable=" + gpsDrawable + ", "
+ "gpsName=" + gpsName + ", "
+ "foregroundDrawable=" + foregroundDrawable + ", "
+ "foregroundName=" + foregroundName + ", "
+ "backgroundDrawable=" + backgroundDrawable + ", "
+ "backgroundName=" + backgroundName + ", "
+ "bearingDrawable=" + bearingDrawable + ", "
+ "bearingName=" + bearingName + ", "
+ "bearingTintColor=" + bearingTintColor + ", "
+ "foregroundTintColor=" + foregroundTintColor + ", "
+ "backgroundTintColor=" + backgroundTintColor + ", "
+ "foregroundStaleTintColor=" + foregroundStaleTintColor + ", "
+ "backgroundStaleTintColor=" + backgroundStaleTintColor + ", "
+ "elevation=" + elevation + ", "
+ "enableStaleState=" + enableStaleState + ", "
+ "staleStateTimeout=" + staleStateTimeout + ", "
+ "padding=" + Arrays.toString(padding) + ", "
+ "maxZoomIconScale=" + maxZoomIconScale + ", "
+ "minZoomIconScale=" + minZoomIconScale + ", "
+ "trackingGesturesManagement=" + trackingGesturesManagement + ", "
+ "trackingInitialMoveThreshold=" + trackingInitialMoveThreshold + ", "
+ "trackingMultiFingerMoveThreshold=" + trackingMultiFingerMoveThreshold + ", "
+ "layerAbove=" + layerAbove
+ "layerBelow=" + layerBelow
+ "trackingAnimationDurationMultiplier=" + trackingAnimationDurationMultiplier
+ "pulseEnabled=" + pulseEnabled
+ "pulseFadeEnabled=" + pulseFadeEnabled
+ "pulseColor=" + pulseColor
+ "pulseSingleDuration=" + pulseSingleDuration
+ "pulseMaxRadius=" + pulseMaxRadius
+ "pulseAlpha=" + pulseAlpha
+ "}";
}
@Override
@@ -847,11 +983,11 @@ public class LocationComponentOptions implements Parcelable {
return false;
}
if (backgroundStaleName != null ? !backgroundStaleName.equals(options.backgroundStaleName) :
options.backgroundStaleName != null) {
options.backgroundStaleName != null) {
return false;
}
if (foregroundStaleName != null ? !foregroundStaleName.equals(options.foregroundStaleName) :
options.foregroundStaleName != null) {
options.foregroundStaleName != null) {
return false;
}
if (gpsName != null ? !gpsName.equals(options.gpsName) : options.gpsName != null) {
@@ -867,23 +1003,23 @@ public class LocationComponentOptions implements Parcelable {
return false;
}
if (bearingTintColor != null ? !bearingTintColor.equals(options.bearingTintColor) :
options.bearingTintColor != null) {
options.bearingTintColor != null) {
return false;
}
if (foregroundTintColor != null ? !foregroundTintColor.equals(options.foregroundTintColor) :
options.foregroundTintColor != null) {
options.foregroundTintColor != null) {
return false;
}
if (backgroundTintColor != null ? !backgroundTintColor.equals(options.backgroundTintColor) :
options.backgroundTintColor != null) {
options.backgroundTintColor != null) {
return false;
}
if (foregroundStaleTintColor != null ? !foregroundStaleTintColor.equals(options.foregroundStaleTintColor) :
options.foregroundStaleTintColor != null) {
options.foregroundStaleTintColor != null) {
return false;
}
if (backgroundStaleTintColor != null ? !backgroundStaleTintColor.equals(options.backgroundStaleTintColor) :
options.backgroundStaleTintColor != null) {
options.backgroundStaleTintColor != null) {
return false;
}
if (!Arrays.equals(padding, options.padding)) {
@@ -892,6 +1028,31 @@ public class LocationComponentOptions implements Parcelable {
if (layerAbove != null ? !layerAbove.equals(options.layerAbove) : options.layerAbove != null) {
return false;
}
if (pulseEnabled != options.pulseEnabled) {
return false;
}
if (pulseFadeEnabled != options.pulseFadeEnabled) {
return false;
}
if (pulseColor != null ? !pulseColor.equals(options.pulseColor) :
options.pulseColor() != null) {
return false;
}
if (Float.compare(options.pulseSingleDuration, pulseSingleDuration) != 0) {
return false;
}
if (Float.compare(options.pulseMaxRadius, pulseMaxRadius) != 0) {
return false;
}
if (Float.compare(options.pulseAlpha, pulseAlpha) != 0) {
return false;
}
return layerBelow != null ? layerBelow.equals(options.layerBelow) : options.layerBelow == null;
}
@@ -924,57 +1085,70 @@ public class LocationComponentOptions implements Parcelable {
result = 31 * result + (minZoomIconScale != +0.0f ? Float.floatToIntBits(minZoomIconScale) : 0);
result = 31 * result + (trackingGesturesManagement ? 1 : 0);
result = 31 * result + (trackingInitialMoveThreshold != +0.0f
? Float.floatToIntBits(trackingInitialMoveThreshold) : 0);
? Float.floatToIntBits(trackingInitialMoveThreshold) : 0);
result = 31 * result + (trackingMultiFingerMoveThreshold != +0.0f
? Float.floatToIntBits(trackingMultiFingerMoveThreshold) : 0);
? Float.floatToIntBits(trackingMultiFingerMoveThreshold) : 0);
result = 31 * result + (layerAbove != null ? layerAbove.hashCode() : 0);
result = 31 * result + (layerBelow != null ? layerBelow.hashCode() : 0);
result = 31 * result + (trackingAnimationDurationMultiplier != +0.0f
? Float.floatToIntBits(trackingAnimationDurationMultiplier) : 0);
? Float.floatToIntBits(trackingAnimationDurationMultiplier) : 0);
result = 31 * result + (compassAnimationEnabled ? 1 : 0);
result = 31 * result + (accuracyAnimationEnabled ? 1 : 0);
result = 31 * result + (pulseEnabled ? 1 : 0);
result = 31 * result + (pulseFadeEnabled ? 1 : 0);
result = 31 * result + (pulseColor != null ? pulseColor.hashCode() : 0);
result = 31 * result + (pulseSingleDuration != +0.0f ? Float.floatToIntBits(pulseSingleDuration) : 0);
result = 31 * result + (pulseMaxRadius != +0.0f ? Float.floatToIntBits(pulseMaxRadius) : 0);
result = 31 * result + (pulseAlpha != +0.0f ? Float.floatToIntBits(pulseAlpha) : 0);
return result;
}
public static final Parcelable.Creator<LocationComponentOptions> CREATOR =
new Parcelable.Creator<LocationComponentOptions>() {
new Parcelable.Creator<LocationComponentOptions>() {
@Override
public LocationComponentOptions createFromParcel(Parcel in) {
public LocationComponentOptions createFromParcel(Parcel in) {
return new LocationComponentOptions(
in.readFloat(),
in.readInt(),
in.readInt(),
in.readInt() == 0 ? in.readString() : null,
in.readInt(),
in.readInt() == 0 ? in.readString() : null,
in.readInt(),
in.readInt() == 0 ? in.readString() : null,
in.readInt(),
in.readInt() == 0 ? in.readString() : null,
in.readInt(),
in.readInt() == 0 ? in.readString() : null,
in.readInt(),
in.readInt() == 0 ? in.readString() : null,
in.readInt() == 0 ? in.readInt() : null,
in.readInt() == 0 ? in.readInt() : null,
in.readInt() == 0 ? in.readInt() : null,
in.readInt() == 0 ? in.readInt() : null,
in.readInt() == 0 ? in.readInt() : null,
in.readFloat(),
in.readInt() == 1,
in.readLong(),
in.createIntArray(),
in.readFloat(),
in.readFloat(),
in.readInt() == 1,
in.readFloat(),
in.readFloat(),
in.readString(),
in.readString(),
in.readFloat(),
in.readInt() == 1,
in.readInt() == 1
);
in.readFloat(),
in.readInt(),
in.readInt(),
in.readInt() == 0 ? in.readString() : null,
in.readInt(),
in.readInt() == 0 ? in.readString() : null,
in.readInt(),
in.readInt() == 0 ? in.readString() : null,
in.readInt(),
in.readInt() == 0 ? in.readString() : null,
in.readInt(),
in.readInt() == 0 ? in.readString() : null,
in.readInt(),
in.readInt() == 0 ? in.readString() : null,
in.readInt() == 0 ? in.readInt() : null,
in.readInt() == 0 ? in.readInt() : null,
in.readInt() == 0 ? in.readInt() : null,
in.readInt() == 0 ? in.readInt() : null,
in.readInt() == 0 ? in.readInt() : null,
in.readFloat(),
in.readInt() == 1,
in.readLong(),
in.createIntArray(),
in.readFloat(),
in.readFloat(),
in.readInt() == 1,
in.readFloat(),
in.readFloat(),
in.readString(),
in.readString(),
in.readFloat(),
in.readInt() == 1,
in.readInt() == 1,
in.readInt() == 1,
in.readInt() == 1,
in.readInt() == 0 ? in.readInt() : null,
in.readFloat(),
in.readFloat(),
in.readFloat(),
null
);
}
@Override
@@ -1073,6 +1247,17 @@ public class LocationComponentOptions implements Parcelable {
dest.writeFloat(trackingAnimationDurationMultiplier);
dest.writeInt(compassAnimationEnabled() ? 1 : 0);
dest.writeInt(accuracyAnimationEnabled() ? 1 : 0);
dest.writeInt(pulseEnabled() ? 1 : 0);
dest.writeInt(pulseFadeEnabled() ? 1 : 0);
if (pulseColor() == null) {
dest.writeInt(1);
} else {
dest.writeInt(0);
dest.writeInt(pulseColor());
}
dest.writeFloat(pulseSingleDuration());
dest.writeFloat(pulseMaxRadius());
dest.writeFloat(pulseAlpha());
}
@Override
@@ -1095,19 +1280,45 @@ public class LocationComponentOptions implements Parcelable {
LocationComponentOptions locationComponentOptions = autoBuild();
if (locationComponentOptions.accuracyAlpha() < 0 || locationComponentOptions.accuracyAlpha() > 1) {
throw new IllegalArgumentException(
"Accuracy alpha value must be between 0.0 and 1.0.");
"Accuracy alpha value must be between 0.0 and 1.0.");
}
if (locationComponentOptions.elevation() < 0f) {
throw new IllegalArgumentException("Invalid shadow size "
+ locationComponentOptions.elevation() + ". Must be >= 0");
+ locationComponentOptions.elevation() + ". Must be >= 0");
}
if (locationComponentOptions.layerAbove() != null && locationComponentOptions.layerBelow() != null) {
throw new IllegalArgumentException("You cannot set both layerAbove and layerBelow options."
+ "Choose one or the other.");
+ " Choose one or the other.");
}
if (locationComponentOptions.pulseEnabled() == null) {
String pulsingSetupError = "";
if (locationComponentOptions.pulseFadeEnabled() != null) {
pulsingSetupError += " pulseFadeEnabled";
}
if (locationComponentOptions.pulseColor() != null) {
pulsingSetupError += " pulseColor";
}
if (locationComponentOptions.pulseSingleDuration() > 0) {
pulsingSetupError += " pulseSingleDuration";
}
if (locationComponentOptions.pulseMaxRadius() > 0) {
pulsingSetupError += " pulseMaxRadius";
}
if (locationComponentOptions.pulseAlpha() >= 0 && locationComponentOptions.pulseAlpha() <= 1) {
pulsingSetupError += " pulseAlpha";
}
if (locationComponentOptions.pulseInterpolator() != null) {
pulsingSetupError += " pulseInterpolator";
}
if (!pulsingSetupError.isEmpty()) {
throw new IllegalStateException("You've set up the following pulsing circle options but have not enabled"
+ " the pulsing circle via the LocationComponentOptions builder:" + pulsingSetupError
+ ". Enable the pulsing circle if you're going to set pulsing options.");
}
}
return locationComponentOptions;
}
@@ -1156,6 +1367,14 @@ public class LocationComponentOptions implements Parcelable {
private Float trackingAnimationDurationMultiplier;
private Boolean compassAnimationEnabled;
private Boolean accuracyAnimationEnabled;
private Boolean pulseEnabled;
private Boolean pulseFadeEnabled;
private int pulseColor;
private float pulseSingleDuration;
private float pulseMaxRadius;
private float pulseAlpha;
@Nullable
private Interpolator pulseInterpolator;
Builder() {
}
@@ -1194,6 +1413,13 @@ public class LocationComponentOptions implements Parcelable {
this.trackingAnimationDurationMultiplier = source.trackingAnimationDurationMultiplier();
this.compassAnimationEnabled = source.compassAnimationEnabled();
this.accuracyAnimationEnabled = source.accuracyAnimationEnabled();
this.pulseEnabled = source.pulseEnabled;
this.pulseFadeEnabled = source.pulseFadeEnabled;
this.pulseColor = source.pulseColor;
this.pulseSingleDuration = source.pulseSingleDuration;
this.pulseMaxRadius = source.pulseMaxRadius;
this.pulseAlpha = source.pulseAlpha;
this.pulseInterpolator = source.pulseInterpolator;
}
/**
@@ -1653,7 +1879,7 @@ public class LocationComponentOptions implements Parcelable {
*/
@NonNull
public LocationComponentOptions.Builder trackingAnimationDurationMultiplier(
float trackingAnimationDurationMultiplier) {
float trackingAnimationDurationMultiplier) {
this.trackingAnimationDurationMultiplier = trackingAnimationDurationMultiplier;
return this;
}
@@ -1674,11 +1900,85 @@ public class LocationComponentOptions implements Parcelable {
*
* @return whether smooth animation of the accuracy circle is enabled
*/
public Builder accuracyAnimationEnabled(Boolean accuracyAnimationEnabled) {
public LocationComponentOptions.Builder accuracyAnimationEnabled(boolean accuracyAnimationEnabled) {
this.accuracyAnimationEnabled = accuracyAnimationEnabled;
return this;
}
/**
* Enable or disable the LocationComponent's pulsing circle.
*
* @return whether the LocationComponent's pulsing circle is enabled
*/
public LocationComponentOptions.Builder pulseEnabled(boolean pulseEnabled) {
this.pulseEnabled = pulseEnabled;
return this;
}
/**
* Enable or disable fading of the LocationComponent's pulsing circle. If it fades, the circle's
* opacity decreases as its radius increases.
*
* @return whether fading of the LocationComponent's pulsing circle is enabled
*/
public LocationComponentOptions.Builder pulseFadeEnabled(boolean pulseFadeEnabled) {
this.pulseFadeEnabled = pulseFadeEnabled;
return this;
}
/**
* Sets the color of the LocationComponent's pulsing circle.
*
* @return the current set color of the circle
*/
public LocationComponentOptions.Builder pulseColor(@ColorInt int pulseColor) {
this.pulseColor = pulseColor;
return this;
}
/**
* Sets the number of milliseconds it takes for a single pulse of the LocationComponent's pulsing circle.
*
* @return the current set length of time for a single pulse
*/
public LocationComponentOptions.Builder pulseSingleDuration(float pulseSingleDuration) {
this.pulseSingleDuration = pulseSingleDuration;
return this;
}
/**
* The maximum radius that a single pulse should expand the LocationComponent's pulsing circle to.
*
* @return the maximum radius that the pulsing circle will expand to.
*/
public LocationComponentOptions.Builder pulseMaxRadius(float pulseMaxRadius) {
this.pulseMaxRadius = pulseMaxRadius;
return this;
}
/**
* Sets the opacity of the LocationComponent's pulsing circle. The expected range is
* 0 to 1. An opacity of 1 makes the layer fully visible.
*
* @return the current opacity of the LocationComponent's pulsing circle
*/
public LocationComponentOptions.Builder pulseAlpha(float pulseAlpha) {
this.pulseAlpha = pulseAlpha;
return this;
}
/**
* Sets the pulsing circle's interpolator animation.
*
* @param pulseInterpolator the type of Android-system interpolator to use when
* creating the pulsing animation
* @return a String which represents the interpolator animation that the pulsing circle will use.
*/
public LocationComponentOptions.Builder pulseInterpolator(Interpolator pulseInterpolator) {
this.pulseInterpolator = pulseInterpolator;
return this;
}
@Nullable
LocationComponentOptions autoBuild() {
String missing = "";
@@ -1740,39 +2040,46 @@ public class LocationComponentOptions implements Parcelable {
throw new IllegalStateException("Missing required properties:" + missing);
}
return new LocationComponentOptions(
this.accuracyAlpha,
this.accuracyColor,
this.backgroundDrawableStale,
this.backgroundStaleName,
this.foregroundDrawableStale,
this.foregroundStaleName,
this.gpsDrawable,
this.gpsName,
this.foregroundDrawable,
this.foregroundName,
this.backgroundDrawable,
this.backgroundName,
this.bearingDrawable,
this.bearingName,
this.bearingTintColor,
this.foregroundTintColor,
this.backgroundTintColor,
this.foregroundStaleTintColor,
this.backgroundStaleTintColor,
this.elevation,
this.enableStaleState,
this.staleStateTimeout,
this.padding,
this.maxZoomIconScale,
this.minZoomIconScale,
trackingGesturesManagement,
this.trackingInitialMoveThreshold,
this.trackingMultiFingerMoveThreshold,
this.layerAbove,
this.layerBelow,
this.trackingAnimationDurationMultiplier,
this.compassAnimationEnabled,
this.accuracyAnimationEnabled);
this.accuracyAlpha,
this.accuracyColor,
this.backgroundDrawableStale,
this.backgroundStaleName,
this.foregroundDrawableStale,
this.foregroundStaleName,
this.gpsDrawable,
this.gpsName,
this.foregroundDrawable,
this.foregroundName,
this.backgroundDrawable,
this.backgroundName,
this.bearingDrawable,
this.bearingName,
this.bearingTintColor,
this.foregroundTintColor,
this.backgroundTintColor,
this.foregroundStaleTintColor,
this.backgroundStaleTintColor,
this.elevation,
this.enableStaleState,
this.staleStateTimeout,
this.padding,
this.maxZoomIconScale,
this.minZoomIconScale,
trackingGesturesManagement,
this.trackingInitialMoveThreshold,
this.trackingMultiFingerMoveThreshold,
this.layerAbove,
this.layerBelow,
this.trackingAnimationDurationMultiplier,
this.compassAnimationEnabled,
this.accuracyAnimationEnabled,
this.pulseEnabled,
this.pulseFadeEnabled,
this.pulseColor,
this.pulseSingleDuration,
this.pulseMaxRadius,
this.pulseAlpha,
this.pulseInterpolator);
}
}
}

View File

@@ -98,6 +98,7 @@ final class LocationLayerController {
styleBitmaps(options);
locationLayerRenderer.styleAccuracy(options.accuracyAlpha(), options.accuracyColor());
styleScaling(options);
locationLayerRenderer.stylePulsingCircle(options);
determineIconsSource(options);
if (!isHidden) {
@@ -258,7 +259,7 @@ final class LocationLayerController {
public void onNewAnimationValue(LatLng value) {
locationLayerRenderer.setLatLng(value);
}
};
};
private final MapboxAnimator.AnimationsValueChangeListener<Float> gpsBearingValueListener =
new MapboxAnimator.AnimationsValueChangeListener<Float>() {
@@ -266,7 +267,7 @@ final class LocationLayerController {
public void onNewAnimationValue(Float value) {
locationLayerRenderer.setGpsBearing(value);
}
};
};
private final MapboxAnimator.AnimationsValueChangeListener<Float> compassBearingValueListener =
new MapboxAnimator.AnimationsValueChangeListener<Float>() {
@@ -274,7 +275,7 @@ final class LocationLayerController {
public void onNewAnimationValue(Float value) {
locationLayerRenderer.setCompassBearing(value);
}
};
};
private final MapboxAnimator.AnimationsValueChangeListener<Float> accuracyValueListener =
new MapboxAnimator.AnimationsValueChangeListener<Float>() {
@@ -282,7 +283,22 @@ final class LocationLayerController {
public void onNewAnimationValue(Float value) {
locationLayerRenderer.setAccuracyRadius(value);
}
};
};
/**
* The listener that handles the updating of the pulsing circle's radius and opacity.
*/
private final MapboxAnimator.AnimationsValueChangeListener<Float> pulsingCircleRadiusListener =
new MapboxAnimator.AnimationsValueChangeListener<Float>() {
@Override
public void onNewAnimationValue(Float newPulseRadiusValue) {
Float newPulseOpacityValue = null;
if (options.pulseFadeEnabled()) {
newPulseOpacityValue = (float) 1 - ((newPulseRadiusValue / 100) * 3);
}
locationLayerRenderer.updatePulsingUi(newPulseRadiusValue, newPulseOpacityValue);
}
};
Set<AnimatorListenerHolder> getAnimationListeners() {
Set<AnimatorListenerHolder> holders = new HashSet<>();
@@ -299,6 +315,10 @@ final class LocationLayerController {
holders.add(new AnimatorListenerHolder(MapboxAnimator.ANIMATOR_LAYER_ACCURACY, accuracyValueListener));
}
if (options.pulseEnabled()) {
holders.add(new AnimatorListenerHolder(MapboxAnimator.ANIMATOR_PULSING_CIRCLE,
pulsingCircleRadiusListener));
}
return holders;
}
@@ -311,4 +331,8 @@ final class LocationLayerController {
void cameraTiltUpdated(double tilt) {
locationLayerRenderer.cameraTiltUpdated(tilt);
}
void adjustPulsingCircleLayerVisibility(boolean visible) {
locationLayerRenderer.adjustPulsingCircleLayerVisibility(visible);
}
}

View File

@@ -38,6 +38,12 @@ interface LocationLayerRenderer {
void setLocationStale(boolean isStale, int renderMode);
void adjustPulsingCircleLayerVisibility(boolean visible);
void stylePulsingCircle(LocationComponentOptions options);
void updatePulsingUi(float radius, @Nullable Float opacity);
void updateIconIds(String foregroundIconString, String foregroundStaleIconString, String backgroundIconString,
String backgroundStaleIconString, String bearingIconString);

View File

@@ -28,7 +28,8 @@ abstract class MapboxAnimator<K> extends ValueAnimator implements ValueAnimator.
ANIMATOR_CAMERA_COMPASS_BEARING,
ANIMATOR_LAYER_ACCURACY,
ANIMATOR_ZOOM,
ANIMATOR_TILT
ANIMATOR_TILT,
ANIMATOR_PULSING_CIRCLE
})
@interface Type {
}
@@ -42,6 +43,7 @@ abstract class MapboxAnimator<K> extends ValueAnimator implements ValueAnimator.
static final int ANIMATOR_LAYER_ACCURACY = 6;
static final int ANIMATOR_ZOOM = 7;
static final int ANIMATOR_TILT = 8;
static final int ANIMATOR_PULSING_CIRCLE = 9;
private final AnimationsValueChangeListener<K> updateListener;
private final K target;
@@ -68,6 +70,15 @@ abstract class MapboxAnimator<K> extends ValueAnimator implements ValueAnimator.
addListener(new AnimatorListener());
}
public MapboxAnimator(AnimationsValueChangeListener<K> updateListener, K target, K animatedValue,
double minUpdateInterval, long timeElapsed) {
this.updateListener = updateListener;
this.target = target;
this.animatedValue = animatedValue;
this.minUpdateInterval = minUpdateInterval;
this.timeElapsed = timeElapsed;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
animatedValue = (K) animation.getAnimatedValue();

View File

@@ -1,5 +1,8 @@
package com.mapbox.mapboxsdk.location;
import android.animation.ValueAnimator;
import android.view.animation.Interpolator;
import androidx.annotation.Nullable;
import com.mapbox.mapboxsdk.geometry.LatLng;
@@ -35,4 +38,31 @@ final class MapboxAnimatorProvider {
@Nullable MapboxMap.CancelableCallback cancelableCallback) {
return new MapboxCameraAnimatorAdapter(values, updateListener, cancelableCallback);
}
/**
* This animator is for the LocationComponent pulsing circle.
*
* @param updateListener the listener that is found in the {@link LocationAnimatorCoordinator}'s
* listener array.
* @param maxAnimationFps the max frames per second of the pulsing animation
* @param pulseSingleDuration the number of milliseconds it takes for the animator to create
* a single pulse.
* @param pulseMaxRadius the max radius when the circle is finished with a single pulse.
* @param pulseInterpolator the type of Android-system interpolator to use for
* the pulsing animation (linear, accelerate, bounce, etc.)
* @return a built {@link PulsingLocationCircleAnimator} object.
*/
PulsingLocationCircleAnimator pulsingCircleAnimator(MapboxAnimator.AnimationsValueChangeListener updateListener,
int maxAnimationFps,
float pulseSingleDuration,
float pulseMaxRadius,
Interpolator pulseInterpolator) {
PulsingLocationCircleAnimator pulsingLocationCircleAnimator =
new PulsingLocationCircleAnimator(updateListener, maxAnimationFps, pulseMaxRadius);
pulsingLocationCircleAnimator.setDuration((long) pulseSingleDuration);
pulsingLocationCircleAnimator.setRepeatMode(ValueAnimator.RESTART);
pulsingLocationCircleAnimator.setRepeatCount(ValueAnimator.INFINITE);
pulsingLocationCircleAnimator.setInterpolator(pulseInterpolator);
return pulsingLocationCircleAnimator;
}
}

View File

@@ -2,9 +2,10 @@ package com.mapbox.mapboxsdk.location;
import android.animation.Animator;
import android.animation.AnimatorSet;
import androidx.annotation.NonNull;
import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import java.util.List;
class MapboxAnimatorSetProvider {

View File

@@ -0,0 +1,19 @@
package com.mapbox.mapboxsdk.location;
/**
* Manages the logic of the interpolated animation which is applied to the LocationComponent's pulsing circle
*/
public class PulsingLocationCircleAnimator extends MapboxFloatAnimator {
/**
*
* @param updateListener the {@link AnimationsValueChangeListener} associated with this animator.
* @param maxAnimationFps the maximum frames per second that the animator should use. Default
* is the {@link LocationAnimatorCoordinator#maxAnimationFps} variable.
*/
public PulsingLocationCircleAnimator(AnimationsValueChangeListener updateListener,
int maxAnimationFps,
float circleMaxRadius) {
super(new Float[]{0f, circleMaxRadius}, updateListener, maxAnimationFps);
}
}

View File

@@ -41,11 +41,19 @@ import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_FOREGROUND_STALE_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_GPS_BEARING;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_LOCATION_STALE;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_PULSING_OPACITY;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_PULSING_RADIUS;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_SHADOW_ICON_OFFSET;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PULSING_CIRCLE_LAYER;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.SHADOW_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.SHADOW_LAYER;
import static com.mapbox.mapboxsdk.style.expressions.Expression.get;
import static com.mapbox.mapboxsdk.style.layers.Property.NONE;
import static com.mapbox.mapboxsdk.style.layers.Property.VISIBLE;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleColor;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleOpacity;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleRadius;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleStrokeColor;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconSize;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.visibility;
import static com.mapbox.mapboxsdk.utils.ColorUtils.colorToRgbaString;
@@ -84,6 +92,7 @@ final class SymbolLocationLayerRenderer implements LocationLayerRenderer {
addSymbolLayer(BACKGROUND_LAYER, FOREGROUND_LAYER);
addSymbolLayer(SHADOW_LAYER, BACKGROUND_LAYER);
addAccuracyLayer();
addPulsingCircleLayerToMap();
}
@Override
@@ -244,6 +253,42 @@ final class SymbolLocationLayerRenderer implements LocationLayerRenderer {
}
}
/**
* Adjust the visibility of the pulsing LocationComponent circle.
*/
@Override
public void adjustPulsingCircleLayerVisibility(boolean visible) {
setLayerVisibility(PULSING_CIRCLE_LAYER, visible);
}
/**
* Adjust the the pulsing LocationComponent circle based on the set options.
*/
@Override
public void stylePulsingCircle(LocationComponentOptions options) {
if (style.getLayer(PULSING_CIRCLE_LAYER) != null) {
setLayerVisibility(PULSING_CIRCLE_LAYER, true);
style.getLayer(PULSING_CIRCLE_LAYER).setProperties(
circleRadius(get(PROPERTY_PULSING_RADIUS)),
circleColor(options.pulseColor()),
circleStrokeColor(options.pulseColor()),
circleOpacity(get(PROPERTY_PULSING_OPACITY))
);
}
}
/**
* Adjust the visual appearance of the pulsing LocationComponent circle.
*/
@Override
public void updatePulsingUi(float radius, @Nullable Float opacity) {
locationFeature.addNumberProperty(PROPERTY_PULSING_RADIUS, radius);
if (opacity != null) {
locationFeature.addNumberProperty(PROPERTY_PULSING_OPACITY, opacity);
}
refreshSource();
}
private void addSymbolLayer(@NonNull String layerId, @NonNull String beforeLayerId) {
Layer layer = layerSourceProvider.generateLayer(layerId);
addLayerToMap(layer, beforeLayerId);
@@ -254,6 +299,14 @@ final class SymbolLocationLayerRenderer implements LocationLayerRenderer {
addLayerToMap(accuracyLayer, BACKGROUND_LAYER);
}
/**
* Add the pulsing LocationComponent circle to the map for future use, if need be.
*/
private void addPulsingCircleLayerToMap() {
Layer pulsingCircleLayer = layerSourceProvider.generatePulsingCircleLayer();
addLayerToMap(pulsingCircleLayer, ACCURACY_LAYER);
}
private void addLayerToMap(Layer layer, @NonNull String idBelowLayer) {
style.addLayerBelow(layer, idBelowLayer);
layerSet.add(layer.getId());

View File

@@ -165,4 +165,13 @@
<public name="mapbox_layer_above" format="string" type="attr" />
<public name="mapbox_layer_below" format="string" type="attr" />
<!-- Pulsing circle -->
<public name="mapbox_pulsingLocationCircleEnabled" format="boolean" type="attr" />
<public name="mapbox_pulsingLocationCircleFadeEnabled" format="boolean" type="attr" />
<public name="mapbox_pulsingLocationCircleColor" format="color" type="attr" />
<public name="mapbox_pulsingLocationCircleDuration" format="float" type="attr" />
<public name="mapbox_pulsingLocationCircleRadius" format="float" type="attr" />
<public name="mapbox_pulsingLocationCircleAlpha" format="float" type="attr" />
<public name="mapbox_pulsingLocationCircleInterpolator" format="string" type="attr" />
</resources>

View File

@@ -188,5 +188,14 @@
<!-- Accuracy animation-->
<attr name="mapbox_accuracyAnimationEnabled" format="boolean" />
<!-- Pulsing circle -->
<attr name="mapbox_pulsingLocationCircleEnabled" format="boolean" />
<attr name="mapbox_pulsingLocationCircleFadeEnabled" format="boolean" />
<attr name="mapbox_pulsingLocationCircleColor" format="color" />
<attr name="mapbox_pulsingLocationCircleDuration" format="float" />
<attr name="mapbox_pulsingLocationCircleRadius" format="float" />
<attr name="mapbox_pulsingLocationCircleAlpha" format="float" />
<attr name="mapbox_pulsingLocationCircleInterpolator" format="string" />
</declare-styleable>
</resources>

View File

@@ -33,5 +33,14 @@
<item name="mapbox_trackingInitialMoveThreshold">@dimen/mapbox_locationComponentTrackingInitialMoveThreshold</item>
<item name="mapbox_trackingMultiFingerMoveThreshold">@dimen/mapbox_locationComponentTrackingMultiFingerMoveThreshold</item>
<!-- Location pulsing circle -->
<item name="mapbox_pulsingLocationCircleEnabled">false</item>
<item name="mapbox_pulsingLocationCircleFadeEnabled">true</item>
<item name="mapbox_pulsingLocationCircleColor">@color/mapbox_location_layer_blue</item>
<item name="mapbox_pulsingLocationCircleDuration">2300</item>
<item name="mapbox_pulsingLocationCircleRadius">35</item>
<item name="mapbox_pulsingLocationCircleAlpha">0.4</item>
<item name="mapbox_pulsingLocationCircleInterpolator">decelerate</item>
</style>
</resources>

View File

@@ -35,7 +35,9 @@ class LocationAnimatorCoordinatorTest {
@Before
fun setUp() {
locationAnimatorCoordinator = LocationAnimatorCoordinator(projection, animatorSetProvider, animatorProvider)
locationAnimatorCoordinator = LocationAnimatorCoordinator(projection,
animatorSetProvider,
animatorProvider)
configureAnimatorProvider()
every { projection.getMetersPerPixelAtLatitude(any()) } answers { 1.0 }
every { animatorSetProvider.startAnimation(any(), any(), any()) } answers {}

View File

@@ -3,6 +3,9 @@ package com.mapbox.mapboxsdk.location;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.view.animation.LinearInterpolator;
import androidx.annotation.NonNull;
import com.mapbox.mapboxsdk.R;
@@ -66,6 +69,44 @@ public class LocationComponentActivationOptionsTest {
assertNotNull(locationComponentActivationOptions);
}
@Test
public void sanityWithDefaultPulsingCircle() throws Exception {
when(style.isFullyLoaded()).thenReturn(true);
LocationComponentOptions locationComponentOptions = LocationComponentOptions.builder(context)
.accuracyAlpha(0.5f)
.pulseEnabled(true)
.build();
assertNotNull(locationComponentOptions);
LocationComponentActivationOptions locationComponentActivationOptions =
LocationComponentActivationOptions.builder(context, style)
.locationComponentOptions(locationComponentOptions)
.useDefaultLocationEngine(true)
.build();
assertNotNull(locationComponentActivationOptions);
}
@Test
public void sanityWithCustomizedPulsingCircle() throws Exception {
when(style.isFullyLoaded()).thenReturn(true);
LocationComponentOptions locationComponentOptions = LocationComponentOptions.builder(context)
.accuracyAlpha(0.5f)
.pulseEnabled(true)
.pulseColor(Color.RED)
.pulseInterpolator(new LinearInterpolator())
.build();
assertNotNull(locationComponentOptions);
LocationComponentActivationOptions locationComponentActivationOptions =
LocationComponentActivationOptions.builder(context, style)
.locationComponentOptions(locationComponentOptions)
.useDefaultLocationEngine(true)
.build();
assertNotNull(locationComponentActivationOptions);
}
@Test
public void includingBothStyleResAndComponentOptions_causesExceptionToBeThrown() throws Exception {

View File

@@ -72,7 +72,7 @@ public class LocationComponentOptionsTest {
public void passingBothLayerPositionOptions_throwsException() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("You cannot set both layerAbove and layerBelow options."
+ "Choose one or the other.");
+ " Choose one or the other.");
LocationComponentOptions.builder(context)
.layerAbove("above")
.layerBelow("below")

View File

@@ -37,6 +37,7 @@ import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_FOREGROUND_ICON_OFFSET;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_GPS_BEARING;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_SHADOW_ICON_OFFSET;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PULSING_CIRCLE_LAYER;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.SHADOW_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.SHADOW_LAYER;
import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_LAYER_ACCURACY;
@@ -167,6 +168,23 @@ public class LocationLayerControllerTest {
verify(style).addLayerBelow(accuracyLayer, BACKGROUND_LAYER);
}
@Test
public void onInitialization_pulsingCircleLayerIsAdded() {
OnRenderModeChangedListener internalRenderModeChangedListener = mock(OnRenderModeChangedListener.class);
LayerSourceProvider sourceProvider = buildLayerProvider();
Layer pulsingCircleLayer = mock(Layer.class);
when(sourceProvider.generatePulsingCircleLayer()).thenReturn(pulsingCircleLayer);
GeoJsonSource locationSource = mock(GeoJsonSource.class);
when(sourceProvider.generateSource(any(Feature.class))).thenReturn(locationSource);
LayerBitmapProvider bitmapProvider = mock(LayerBitmapProvider.class);
LocationComponentOptions options = mock(LocationComponentOptions.class);
new LocationLayerController(mapboxMap, mapboxMap.getStyle(), sourceProvider, buildFeatureProvider(options),
bitmapProvider, options, internalRenderModeChangedListener, false);
verify(style).addLayerBelow(pulsingCircleLayer, ACCURACY_LAYER);
}
@Test
public void onInitialization_numberOfCachedLayerIdsIsConstant() {
OnRenderModeChangedListener internalRenderModeChangedListener = mock(OnRenderModeChangedListener.class);
@@ -184,7 +202,7 @@ public class LocationLayerControllerTest {
controller.initializeComponents(mapboxMap.getStyle(), options);
assertEquals(5, layerSet.size());
assertEquals(6, layerSet.size());
}
@Test
@@ -402,7 +420,7 @@ public class LocationLayerControllerTest {
layerController.applyStyle(options);
verify(style, times(0)).removeLayer(any(String.class));
verify(style, times(5)).addLayerBelow(any(Layer.class), any(String.class));
verify(style, times(6)).addLayerBelow(any(Layer.class), any(String.class));
}
@Test
@@ -424,7 +442,7 @@ public class LocationLayerControllerTest {
verify(style, times(0)).removeLayer(any(String.class));
verify(style, times(1)).addLayer(any(Layer.class));
verify(style, times(4)).addLayerBelow(any(Layer.class), Mockito.<String>any());
verify(style, times(5)).addLayerBelow(any(Layer.class), Mockito.<String>any());
}
@Test
@@ -726,6 +744,9 @@ public class LocationLayerControllerTest {
Layer accuracyLayer = mock(Layer.class);
when(accuracyLayer.getId()).thenReturn(ACCURACY_LAYER);
when(layerSourceProvider.generateAccuracyLayer()).thenReturn(accuracyLayer);
Layer pulsingCircleLayer = mock(Layer.class);
when(pulsingCircleLayer.getId()).thenReturn(PULSING_CIRCLE_LAYER);
when(layerSourceProvider.generatePulsingCircleLayer()).thenReturn(pulsingCircleLayer);
return layerSourceProvider;
}

View File

@@ -2,6 +2,7 @@ package com.mapbox.mapboxsdk.location
import android.Manifest
import android.content.Context
import android.graphics.Color
import android.location.Location
import androidx.test.annotation.UiThreadTest
import androidx.test.espresso.Espresso.onView
@@ -123,11 +124,49 @@ class LocationLayerControllerTest : EspressoTest() {
assertThat(mapboxMap.isLayerVisible(SHADOW_LAYER), `is`(true))
assertThat(mapboxMap.isLayerVisible(ACCURACY_LAYER), `is`(true))
assertThat(mapboxMap.isLayerVisible(BEARING_LAYER), `is`(false))
assertThat(mapboxMap.isLayerVisible(PULSING_CIRCLE_LAYER), `is`(false))
}
}
executeComponentTest(componentAction)
}
@Test
fun onMapChange_locationComponentPulsingCircleLayerGetsRedrawn() {
val componentAction = object : LocationComponentAction.OnPerformLocationComponentAction {
override fun onLocationComponentAction(
component: LocationComponent,
mapboxMap: MapboxMap,
style: Style,
uiController: UiController,
context: Context
) {
component.activateLocationComponent(
LocationComponentActivationOptions
.builder(context, style)
.locationComponentOptions(LocationComponentOptions.builder(context)
.pulseEnabled(true)
.build())
.useDefaultLocationEngine(false)
.build()
)
component.isLocationComponentEnabled = true
component.renderMode = RenderMode.NORMAL
component.forceLocationUpdate(location)
styleChangeIdlingResource.waitForStyle(mapboxMap, Style.LIGHT)
TestingAsyncUtils.waitForLayer(uiController, mapView)
assertThat(component.renderMode, `is`(equalTo(RenderMode.NORMAL)))
// Check that the Source has been re-added to the new map style
val source: GeoJsonSource? = mapboxMap.style!!.getSourceAs(LOCATION_SOURCE)
assertThat(source, notNullValue())
// Check that the pulsing circle layer visibilities is set to visible
assertThat(mapboxMap.isLayerVisible(PULSING_CIRCLE_LAYER), `is`(true))
}
}
}
@Test
fun renderModeCompass_bearingLayersDoGetAdded() {
val componentAction = object : LocationComponentAction.OnPerformLocationComponentAction {
@@ -159,6 +198,137 @@ class LocationLayerControllerTest : EspressoTest() {
executeComponentTest(componentAction)
}
@Test
fun pulsingCircle_enableLocationComponent_pulsingLayerVisibility() {
val componentAction = object : LocationComponentAction.OnPerformLocationComponentAction {
override fun onLocationComponentAction(
component: LocationComponent,
mapboxMap: MapboxMap,
style: Style,
uiController: UiController,
context: Context
) {
component.activateLocationComponent(
LocationComponentActivationOptions
.builder(context, style)
.useDefaultLocationEngine(false)
.build()
)
component.isLocationComponentEnabled = true
component.forceLocationUpdate(location)
TestingAsyncUtils.waitForLayer(uiController, mapView)
component.applyStyle(LocationComponentOptions.builder(context)
.pulseEnabled(true).build())
assertThat(mapboxMap.isLayerVisible(PULSING_CIRCLE_LAYER), `is`(true))
}
}
executeComponentTest(componentAction)
}
@Test
fun pulsingCircle_disableLocationComponent_pulsingLayerVisibility() {
val componentAction = object : LocationComponentAction.OnPerformLocationComponentAction {
override fun onLocationComponentAction(
component: LocationComponent,
mapboxMap: MapboxMap,
style: Style,
uiController: UiController,
context: Context
) {
component.activateLocationComponent(
LocationComponentActivationOptions
.builder(context, style)
.useDefaultLocationEngine(false)
.build()
)
component.isLocationComponentEnabled = false
component.forceLocationUpdate(location)
TestingAsyncUtils.waitForLayer(uiController, mapView)
assertThat(mapboxMap.isLayerVisible(PULSING_CIRCLE_LAYER), `is`(false))
}
}
executeComponentTest(componentAction)
}
@Test
fun pulsingCircle_changeColorCheck() {
val componentAction = object : LocationComponentAction.OnPerformLocationComponentAction {
override fun onLocationComponentAction(
component: LocationComponent,
mapboxMap: MapboxMap,
style: Style,
uiController: UiController,
context: Context
) {
component.activateLocationComponent(
LocationComponentActivationOptions
.builder(context, style)
.useDefaultLocationEngine(false)
.build()
)
component.isLocationComponentEnabled = true
component.forceLocationUpdate(location)
TestingAsyncUtils.waitForLayer(uiController, mapView)
component.applyStyle(LocationComponentOptions.builder(context)
.pulseEnabled(true)
.pulseColor(Color.RED)
.build())
component.applyStyle(LocationComponentOptions.builder(context)
.pulseEnabled(true)
.pulseColor(Color.BLUE)
.build())
mapboxMap.style.apply {
assertThat(component.locationComponentOptions.pulseColor(), `is`(Color.BLUE))
}
}
}
executeComponentTest(componentAction)
}
@Test
fun pulsingCircle_changeSpeedCheck() {
val componentAction = object : LocationComponentAction.OnPerformLocationComponentAction {
override fun onLocationComponentAction(
component: LocationComponent,
mapboxMap: MapboxMap,
style: Style,
uiController: UiController,
context: Context
) {
component.activateLocationComponent(
LocationComponentActivationOptions
.builder(context, style)
.useDefaultLocationEngine(false)
.build()
)
component.isLocationComponentEnabled = true
component.forceLocationUpdate(location)
TestingAsyncUtils.waitForLayer(uiController, mapView)
component.applyStyle(LocationComponentOptions.builder(context)
.pulseEnabled(true)
.pulseSingleDuration(8000f)
.build())
component.applyStyle(LocationComponentOptions.builder(context)
.pulseEnabled(true)
.pulseSingleDuration(400f)
.build())
mapboxMap.style.apply {
assertThat(component.locationComponentOptions.pulseSingleDuration(), `is`(400f))
}
}
}
executeComponentTest(componentAction)
}
@Test
fun renderModeGps_navigationLayersDoGetAdded() {
val componentAction = object : LocationComponentAction.OnPerformLocationComponentAction {

View File

@@ -33,7 +33,7 @@ class StyleChangeIdlingResource : IdlingResource {
fun waitForStyle(mapboxMap: MapboxMap, styleUrl: String) {
isIdle = false
mapboxMap.setStyle(Style.Builder().fromUrl(styleUrl)) {
mapboxMap.setStyle(Style.Builder().fromUri(styleUrl)) {
setIdle()
}
}

View File

@@ -1015,6 +1015,28 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity" />
</activity>
<activity
android:name=".activity.location.BasicLocationPulsingCircleActivity"
android:description="@string/description_location_basic_pulsing_circle"
android:label="@string/activity_basic_location_pulsing_circle">
<meta-data
android:name="@string/category"
android:value="@string/category_location" />
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity" />
</activity>
<activity
android:name=".activity.location.CustomizedLocationPulsingCircleActivity"
android:description="@string/description_location_customized_pulsing_circle"
android:label="@string/activity_customized_location_pulsing_circle">
<meta-data
android:name="@string/category"
android:value="@string/category_location" />
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity" />
</activity>
<activity
android:name=".activity.location.LocationFragmentActivity"
android:description="@string/description_location_fragment"

View File

@@ -0,0 +1,215 @@
package com.mapbox.mapboxsdk.testapp.activity.location;
import android.annotation.SuppressLint;
import android.location.Location;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import com.mapbox.android.core.location.LocationEngineRequest;
import com.mapbox.android.core.permissions.PermissionsListener;
import com.mapbox.android.core.permissions.PermissionsManager;
import com.mapbox.mapboxsdk.location.LocationComponent;
import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions;
import com.mapbox.mapboxsdk.location.LocationComponentOptions;
import com.mapbox.mapboxsdk.location.modes.CameraMode;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.testapp.R;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import static com.mapbox.mapboxsdk.testapp.activity.location.Utils.getNextStyle;
/**
* This activity shows a basic usage of the LocationComponent's pulsing circle. There's no
* customization of the pulsing circle's color, radius, speed, etc.
*/
public class BasicLocationPulsingCircleActivity extends AppCompatActivity implements OnMapReadyCallback {
private static final String SAVED_STATE_LOCATION = "saved_state_location";
private static final String TAG = "Mbgl-BasicLocationPulsingCircleActivity";
private Location lastLocation;
private MapView mapView;
private PermissionsManager permissionsManager;
private LocationComponent locationComponent;
private MapboxMap mapboxMap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_location_layer_basic_pulsing_circle);
mapView = findViewById(R.id.mapView);
if (savedInstanceState != null) {
lastLocation = savedInstanceState.getParcelable(SAVED_STATE_LOCATION);
}
mapView.onCreate(savedInstanceState);
checkPermissions();
}
@SuppressLint("MissingPermission")
@Override
public void onMapReady(@NonNull MapboxMap mapboxMap) {
this.mapboxMap = mapboxMap;
mapboxMap.setStyle(Style.MAPBOX_STREETS, style -> {
locationComponent = mapboxMap.getLocationComponent();
LocationComponentOptions locationComponentOptions =
LocationComponentOptions.builder(BasicLocationPulsingCircleActivity.this)
.pulseEnabled(true)
.build();
LocationComponentActivationOptions locationComponentActivationOptions =
buildLocationComponentActivationOptions(style,locationComponentOptions);
locationComponent.activateLocationComponent(locationComponentActivationOptions);
locationComponent.setLocationComponentEnabled(true);
locationComponent.setCameraMode(CameraMode.TRACKING);
locationComponent.forceLocationUpdate(lastLocation);
});
}
private LocationComponentActivationOptions buildLocationComponentActivationOptions(@NonNull Style style,
@NonNull LocationComponentOptions
locationComponentOptions) {
return LocationComponentActivationOptions
.builder(this, style)
.locationComponentOptions(locationComponentOptions)
.useDefaultLocationEngine(true)
.locationEngineRequest(new LocationEngineRequest.Builder(750)
.setFastestInterval(750)
.setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
.build())
.build();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_pulsing_location_mode, menu);
return true;
}
@SuppressLint("MissingPermission")
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (locationComponent == null) {
return super.onOptionsItemSelected(item);
}
int id = item.getItemId();
if (id == R.id.action_map_style_change) {
loadNewStyle();
return true;
} else if (id == R.id.action_component_disable) {
locationComponent.setLocationComponentEnabled(false);
return true;
} else if (id == R.id.action_component_enabled) {
locationComponent.setLocationComponentEnabled(true);
return true;
} else if (id == R.id.action_stop_pulsing) {
locationComponent.applyStyle(LocationComponentOptions.builder(
BasicLocationPulsingCircleActivity.this)
.pulseEnabled(false)
.build());
return true;
} else if (id == R.id.action_start_pulsing) {
locationComponent.applyStyle(LocationComponentOptions.builder(
BasicLocationPulsingCircleActivity.this)
.pulseEnabled(true)
.build());
return true;
}
return super.onOptionsItemSelected(item);
}
private void loadNewStyle() {
mapboxMap.setStyle(new Style.Builder().fromUri(getNextStyle()));
}
private void checkPermissions() {
if (PermissionsManager.areLocationPermissionsGranted(this)) {
mapView.getMapAsync(this);
} else {
permissionsManager = new PermissionsManager(new PermissionsListener() {
@Override
public void onExplanationNeeded(List<String> permissionsToExplain) {
Toast.makeText(BasicLocationPulsingCircleActivity.this, "You need to accept location permissions.",
Toast.LENGTH_SHORT).show();
}
@Override
public void onPermissionResult(boolean granted) {
if (granted) {
mapView.getMapAsync(BasicLocationPulsingCircleActivity.this);
} else {
finish();
}
}
});
permissionsManager.requestLocationPermissions(this);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected void onStart() {
super.onStart();
mapView.onStart();
}
@Override
protected void onResume() {
super.onResume();
mapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
mapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mapView.onStop();
}
@SuppressLint("MissingPermission")
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
if (locationComponent != null) {
outState.putParcelable(SAVED_STATE_LOCATION, locationComponent.getLastKnownLocation());
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mapView.onDestroy();
}
@Override
public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}
}

View File

@@ -0,0 +1,360 @@
package com.mapbox.mapboxsdk.testapp.activity.location;
import android.annotation.SuppressLint;
import android.graphics.Color;
import android.location.Location;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Toast;
import com.mapbox.android.core.location.LocationEngineRequest;
import com.mapbox.android.core.permissions.PermissionsListener;
import com.mapbox.android.core.permissions.PermissionsManager;
import com.mapbox.mapboxsdk.location.LocationComponent;
import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions;
import com.mapbox.mapboxsdk.location.LocationComponentOptions;
import com.mapbox.mapboxsdk.location.modes.CameraMode;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.testapp.R;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.ListPopupWindow;
import static com.mapbox.mapboxsdk.testapp.activity.location.Utils.getNextStyle;
/**
* This activity shows how to customize the LocationComponent's pulsing circle.
*/
public class CustomizedLocationPulsingCircleActivity extends AppCompatActivity implements OnMapReadyCallback {
//region
// Adjust these variables to customize the example's pulsing circle UI
private static final float DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS = 2300;
private static final float SECOND_LOCATION_CIRCLE_PULSE_DURATION_MS = 800;
private static final float THIRD_LOCATION_CIRCLE_PULSE_DURATION_MS = 8000;
private static final float DEFAULT_LOCATION_CIRCLE_PULSE_RADIUS = 35;
private static final float DEFAULT_LOCATION_CIRCLE_PULSE_ALPHA = .55f;
private static final Interpolator DEFAULT_LOCATION_CIRCLE_INTERPOLATOR_PULSE_MODE
= new DecelerateInterpolator();
private static final boolean DEFAULT_LOCATION_CIRCLE_PULSE_FADE_MODE = true;
//endregion
//region
private static int LOCATION_CIRCLE_PULSE_COLOR;
private static float LOCATION_CIRCLE_PULSE_DURATION = DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS;
private static final String SAVED_STATE_LOCATION = "saved_state_location";
private static final String SAVED_STATE_LOCATION_CIRCLE_PULSE_COLOR = "saved_state_color";
private static final String SAVED_STATE_LOCATION_CIRCLE_PULSE_DURATION = "saved_state_duration";
private static final String LAYER_BELOW_ID = "waterway-label";
private Location lastLocation;
private MapView mapView;
private Button pulsingCircleDurationButton;
private Button pulsingCircleColorButton;
private PermissionsManager permissionsManager;
private LocationComponent locationComponent;
private MapboxMap mapboxMap;
private float currentPulseDuration;
//endregion
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_location_layer_customized_pulsing_circle);
LOCATION_CIRCLE_PULSE_COLOR = Color.BLUE;
mapView = findViewById(R.id.mapView);
if (savedInstanceState != null) {
lastLocation = savedInstanceState.getParcelable(SAVED_STATE_LOCATION);
LOCATION_CIRCLE_PULSE_COLOR = savedInstanceState.getInt(SAVED_STATE_LOCATION_CIRCLE_PULSE_COLOR);
LOCATION_CIRCLE_PULSE_DURATION = savedInstanceState.getFloat(SAVED_STATE_LOCATION_CIRCLE_PULSE_DURATION);
}
pulsingCircleDurationButton = findViewById(R.id.button_location_circle_duration);
pulsingCircleDurationButton.setText(String.format("%sms",
String.valueOf(LOCATION_CIRCLE_PULSE_DURATION)));
pulsingCircleDurationButton.setOnClickListener(v -> {
if (locationComponent == null) {
return;
}
showDurationListDialog();
});
pulsingCircleColorButton = findViewById(R.id.button_location_circle_color);
pulsingCircleColorButton.setOnClickListener(v -> {
if (locationComponent == null) {
return;
}
showColorListDialog();
});
mapView.onCreate(savedInstanceState);
checkPermissions();
}
@SuppressLint("MissingPermission")
@Override
public void onMapReady(@NonNull MapboxMap mapboxMap) {
this.mapboxMap = mapboxMap;
mapboxMap.setStyle(Style.MAPBOX_STREETS, style -> {
locationComponent = mapboxMap.getLocationComponent();
LocationComponentOptions locationComponentOptions =
buildLocationComponentOptions(
LOCATION_CIRCLE_PULSE_COLOR,
LOCATION_CIRCLE_PULSE_DURATION)
.pulseEnabled(true)
.build();
LocationComponentActivationOptions locationComponentActivationOptions =
buildLocationComponentActivationOptions(style,locationComponentOptions);
locationComponent.activateLocationComponent(locationComponentActivationOptions);
locationComponent.setLocationComponentEnabled(true);
locationComponent.setCameraMode(CameraMode.TRACKING);
locationComponent.forceLocationUpdate(lastLocation);
});
}
private LocationComponentOptions.Builder buildLocationComponentOptions(int pulsingCircleColor,
float pulsingCircleDuration
) {
currentPulseDuration = pulsingCircleDuration;
return LocationComponentOptions.builder(this)
.layerBelow(LAYER_BELOW_ID)
.pulseFadeEnabled(DEFAULT_LOCATION_CIRCLE_PULSE_FADE_MODE)
.pulseInterpolator(DEFAULT_LOCATION_CIRCLE_INTERPOLATOR_PULSE_MODE)
.pulseColor(pulsingCircleColor)
.pulseAlpha(DEFAULT_LOCATION_CIRCLE_PULSE_ALPHA)
.pulseSingleDuration(pulsingCircleDuration)
.pulseMaxRadius(DEFAULT_LOCATION_CIRCLE_PULSE_RADIUS);
}
@SuppressLint("MissingPermission")
private void setNewLocationComponentOptions(float newPulsingDuration,
int newPulsingColor) {
mapboxMap.getStyle(style -> locationComponent.applyStyle(
buildLocationComponentOptions(
newPulsingColor,
newPulsingDuration)
.pulseEnabled(true)
.build()));
}
private LocationComponentActivationOptions buildLocationComponentActivationOptions(
@NonNull Style style,
@NonNull LocationComponentOptions locationComponentOptions) {
return LocationComponentActivationOptions
.builder(this, style)
.locationComponentOptions(locationComponentOptions)
.useDefaultLocationEngine(true)
.locationEngineRequest(new LocationEngineRequest.Builder(750)
.setFastestInterval(750)
.setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
.build())
.build();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_pulsing_location_mode, menu);
return true;
}
@SuppressLint("MissingPermission")
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (locationComponent == null) {
return super.onOptionsItemSelected(item);
}
int id = item.getItemId();
if (id == R.id.action_map_style_change) {
loadNewStyle();
return true;
} else if (id == R.id.action_component_disable) {
locationComponent.setLocationComponentEnabled(false);
return true;
} else if (id == R.id.action_component_enabled) {
locationComponent.setLocationComponentEnabled(true);
return true;
} else if (id == R.id.action_stop_pulsing) {
locationComponent.applyStyle(LocationComponentOptions.builder(
CustomizedLocationPulsingCircleActivity.this)
.pulseEnabled(false)
.build());
return true;
} else if (id == R.id.action_start_pulsing) {
locationComponent.applyStyle(buildLocationComponentOptions(
LOCATION_CIRCLE_PULSE_COLOR,
LOCATION_CIRCLE_PULSE_DURATION)
.pulseEnabled(true)
.build());
return true;
}
return super.onOptionsItemSelected(item);
}
private void loadNewStyle() {
mapboxMap.setStyle(new Style.Builder().fromUri(getNextStyle()));
}
private void checkPermissions() {
if (PermissionsManager.areLocationPermissionsGranted(this)) {
mapView.getMapAsync(this);
} else {
permissionsManager = new PermissionsManager(new PermissionsListener() {
@Override
public void onExplanationNeeded(List<String> permissionsToExplain) {
Toast.makeText(CustomizedLocationPulsingCircleActivity.this, "You need to accept location permissions.",
Toast.LENGTH_SHORT).show();
}
@Override
public void onPermissionResult(boolean granted) {
if (granted) {
mapView.getMapAsync(CustomizedLocationPulsingCircleActivity.this);
} else {
finish();
}
}
});
permissionsManager.requestLocationPermissions(this);
}
}
private void showDurationListDialog() {
List<String> modes = new ArrayList<>();
modes.add(String.format("%sms", String.valueOf(DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS)));
modes.add(String.format("%sms", String.valueOf(SECOND_LOCATION_CIRCLE_PULSE_DURATION_MS)));
modes.add(String.format("%sms", String.valueOf(THIRD_LOCATION_CIRCLE_PULSE_DURATION_MS)));
ArrayAdapter<String> profileAdapter = new ArrayAdapter<>(this,
android.R.layout.simple_list_item_1, modes);
ListPopupWindow listPopup = new ListPopupWindow(this);
listPopup.setAdapter(profileAdapter);
listPopup.setAnchorView(pulsingCircleDurationButton);
listPopup.setOnItemClickListener((parent, itemView, position, id) -> {
String selectedMode = modes.get(position);
pulsingCircleDurationButton.setText(selectedMode);
if (selectedMode.contentEquals(String.format("%sms",
String.valueOf(DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS)))) {
LOCATION_CIRCLE_PULSE_DURATION = DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS;
setNewLocationComponentOptions(DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS, LOCATION_CIRCLE_PULSE_COLOR);
} else if (selectedMode.contentEquals(String.format("%sms",
String.valueOf(SECOND_LOCATION_CIRCLE_PULSE_DURATION_MS)))) {
LOCATION_CIRCLE_PULSE_DURATION = SECOND_LOCATION_CIRCLE_PULSE_DURATION_MS;
setNewLocationComponentOptions(SECOND_LOCATION_CIRCLE_PULSE_DURATION_MS, LOCATION_CIRCLE_PULSE_COLOR);
} else if (selectedMode.contentEquals(String.format("%sms",
String.valueOf(THIRD_LOCATION_CIRCLE_PULSE_DURATION_MS)))) {
LOCATION_CIRCLE_PULSE_DURATION = THIRD_LOCATION_CIRCLE_PULSE_DURATION_MS;
setNewLocationComponentOptions(THIRD_LOCATION_CIRCLE_PULSE_DURATION_MS, LOCATION_CIRCLE_PULSE_COLOR);
}
listPopup.dismiss();
});
listPopup.show();
}
private void showColorListDialog() {
List<String> trackingTypes = new ArrayList<>();
trackingTypes.add("Blue");
trackingTypes.add("Red");
trackingTypes.add("Green");
trackingTypes.add("Gray");
ArrayAdapter<String> profileAdapter = new ArrayAdapter<>(this,
android.R.layout.simple_list_item_1, trackingTypes);
ListPopupWindow listPopup = new ListPopupWindow(this);
listPopup.setAdapter(profileAdapter);
listPopup.setAnchorView(pulsingCircleColorButton);
listPopup.setOnItemClickListener((parent, itemView, position, id) -> {
String selectedTrackingType = trackingTypes.get(position);
pulsingCircleColorButton.setText(selectedTrackingType);
if (selectedTrackingType.contentEquals("Blue")) {
LOCATION_CIRCLE_PULSE_COLOR = Color.BLUE;
setNewLocationComponentOptions(currentPulseDuration, Color.BLUE);
} else if (selectedTrackingType.contentEquals("Red")) {
LOCATION_CIRCLE_PULSE_COLOR = Color.RED;
setNewLocationComponentOptions(currentPulseDuration, Color.RED);
} else if (selectedTrackingType.contentEquals("Green")) {
LOCATION_CIRCLE_PULSE_COLOR = Color.GREEN;
setNewLocationComponentOptions(currentPulseDuration, Color.GREEN);
} else if (selectedTrackingType.contentEquals("Gray")) {
LOCATION_CIRCLE_PULSE_COLOR = Color.parseColor("#4a4a4a");
setNewLocationComponentOptions(currentPulseDuration, Color.parseColor("#4a4a4a"));
}
listPopup.dismiss();
});
listPopup.show();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected void onStart() {
super.onStart();
mapView.onStart();
}
@Override
protected void onResume() {
super.onResume();
mapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
mapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mapView.onStop();
}
@SuppressLint("MissingPermission")
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
if (locationComponent != null) {
outState.putParcelable(SAVED_STATE_LOCATION, locationComponent.getLastKnownLocation());
outState.putInt(SAVED_STATE_LOCATION_CIRCLE_PULSE_COLOR, LOCATION_CIRCLE_PULSE_COLOR);
outState.putFloat(SAVED_STATE_LOCATION_CIRCLE_PULSE_DURATION, LOCATION_CIRCLE_PULSE_DURATION);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mapView.onDestroy();
}
@Override
public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}
}

View File

@@ -10,7 +10,7 @@ import java.util.Random;
import timber.log.Timber;
/**
* Useful utilities used throughout the testapp.
* Useful utilities used throughout the test app.
*/
public class Utils {

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.mapbox.mapboxsdk.maps.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:mapbox_uiAttribution="false"/>
</FrameLayout>

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.mapbox.mapboxsdk.maps.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="0dp"
app:layout_constraintBottom_toTopOf="@+id/linearLayout"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:mapbox_uiAttribution="false" />
<LinearLayout
android:id="@+id/linearLayout"
style="?android:attr/buttonBarStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/primary"
android:orientation="horizontal"
android:weightSum="4"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
tools:layout_constraintBottom_creator="1"
tools:layout_constraintLeft_creator="1"
tools:layout_constraintRight_creator="1">
<TextView
android:id="@+id/tv_frequency"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".75"
android:gravity="center"
android:text="Duration:"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
<Button
android:id="@+id/button_location_circle_duration"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.25"
android:gravity="center"
tools:text="400ms"
android:textColor="@android:color/white" />
<TextView
android:id="@+id/tv_color"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".85"
android:gravity="center"
android:text="Color:"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
<Button
android:id="@+id/button_location_circle_color"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.15"
android:gravity="center"
android:text="None"
android:textColor="@android:color/white" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_map_style_change"
android:title="Toggle custom Map style"
app:showAsAction="never"/>
<item android:id="@+id/action_component_disable"
android:title="Disable Component"
app:showAsAction="never"/>
<item android:id="@+id/action_component_enabled"
android:title="Enable Component"
app:showAsAction="never"/>
<item android:id="@+id/action_stop_pulsing"
android:title="Stop pulsing"
app:showAsAction="never"/>
<item android:id="@+id/action_start_pulsing"
android:title="Start pulsing"
app:showAsAction="never"/>
</menu>

View File

@@ -85,6 +85,8 @@
<string name="description_location_map_change">Change map\'s style while location is displayed</string>
<string name="description_location_modes">Showcases location render and tracking modes</string>
<string name="description_location_fragment">Uses LocationComponent in a Fragment</string>
<string name="description_location_basic_pulsing_circle">Display the LocationComponent\'s default pulsing circle</string>
<string name="description_location_customized_pulsing_circle">Display and customize the LocationComponent\'s pulsing circle</string>
<string name="description_location_manual">Force location updates and don\'t rely on the engine</string>
<string name="description_location_activation_builder">Use LocationComponentActivationOptions to set options</string>
<string name="description_recyclerview_textureview">Show multiple TextureView MapViews as recyclerView items</string>

View File

@@ -81,6 +81,8 @@
<string name="activity_draggable_maker">Draggable marker</string>
<string name="activity_location_map_change">Simple Location Activity</string>
<string name="activity_location_modes">Location Modes Activity</string>
<string name="activity_basic_location_pulsing_circle">Basic Pulsing Circle Activity</string>
<string name="activity_customized_location_pulsing_circle">Customized Pulsing Circle Activity</string>
<string name="activity_location_fragment">Location Fragment</string>
<string name="activity_location_manual">Manual Location updates</string>
<string name="activity_location_activation_builder">Build Location Activation</string>