summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMikhail Pozdnyakov <mikhail.pozdnyakov@mapbox.com>2019-01-18 13:12:04 +0200
committerlangsmith <langstonlmcs@gmail.com>2019-04-09 17:25:35 -0700
commit194094e769fbbc0c25a5a6aa86db123528b70c86 (patch)
tree40a5dc5e3cb06ccc7c05ffff3f02d8614e153c64
parent8389e746b6745a68fcd58ece8e398bde0a85b57f (diff)
downloadqtlocation-mapboxgl-194094e769fbbc0c25a5a6aa86db123528b70c86.tar.gz
[android] initial additions to add a pulsing locationComponent circle
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LayerSourceProvider.java10
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java41
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java9
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentConstants.java5
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java302
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationLayerController.java32
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/PulsingLocationCircleAnimator.java80
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/Utils.java8
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/modes/PulseMode.java46
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml61
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml9
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml6
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml11
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MapboxApplication.java2
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/location/LocationPulsingCircleActivity.java324
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_location_layer_pulsing_circle.xml78
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_location_pulsing_circle_styling.xml28
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml1
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml1
19 files changed, 1014 insertions, 40 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LayerSourceProvider.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LayerSourceProvider.java
index cac513c2f9..00afe82816 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LayerSourceProvider.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LayerSourceProvider.java
@@ -1,6 +1,7 @@
package com.mapbox.mapboxsdk.location;
import android.support.annotation.NonNull;
+
import com.mapbox.geojson.Feature;
import com.mapbox.mapboxsdk.style.layers.CircleLayer;
import com.mapbox.mapboxsdk.style.layers.Layer;
@@ -27,6 +28,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.PROPERTY_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;
@@ -108,4 +110,12 @@ class LayerSourceProvider {
circlePitchAlignment(Property.CIRCLE_PITCH_ALIGNMENT_MAP)
);
}
+
+ @NonNull
+ Layer generatePulsingCircleLayer() {
+ return new CircleLayer(PROPERTY_PULSING_CIRCLE_LAYER, LOCATION_SOURCE)
+ .withProperties(
+ circlePitchAlignment(Property.CIRCLE_PITCH_ALIGNMENT_MAP)
+ );
+ }
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java
index 35e5efc266..c6c9340c06 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java
@@ -7,10 +7,15 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.SparseArray;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.BounceInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.geometry.LatLng;
+import com.mapbox.mapboxsdk.location.modes.PulseMode;
import com.mapbox.mapboxsdk.log.Logger;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.Projection;
@@ -51,6 +56,7 @@ final class LocationAnimatorCoordinator {
private final MapboxAnimatorSetProvider animatorSetProvider;
private boolean compassAnimationEnabled;
private boolean accuracyAnimationEnabled;
+ private PulsingLocationCircleAnimator pulsingLocationCircleAnimator;
@VisibleForTesting
int maxAnimationFps = Integer.MAX_VALUE;
@@ -147,6 +153,16 @@ final class LocationAnimatorCoordinator {
playAnimators(animationDuration, ANIMATOR_TILT);
}
+ void startLocationCirclePulsing(LocationComponentOptions options, MapboxMap mapboxMap) {
+ pulsingLocationCircleAnimator = new PulsingLocationCircleAnimator();
+ pulsingLocationCircleAnimator.animatePulsingCircleRadius(
+ retrievePulseInterpolator(options.pulseInterpolator()), mapboxMap, options);
+ }
+
+ void stopPulsingAnimation() {
+ pulsingLocationCircleAnimator.stopPulsingAnimation();
+ }
+
private LatLng getPreviousLayerLatLng() {
LatLng previousLatLng;
MapboxAnimator latLngAnimator = animatorArray.get(ANIMATOR_LAYER_LATLNG);
@@ -381,11 +397,26 @@ final class LocationAnimatorCoordinator {
this.accuracyAnimationEnabled = accuracyAnimationEnabled;
}
- void setMaxAnimationFps(int maxAnimationFps) {
- if (maxAnimationFps <= 0) {
- Logger.e(TAG, "Max animation FPS cannot be less or equal to 0.");
- return;
+ void setMaxAnimationFps(int maxAnimationFps){
+ if (maxAnimationFps <= 0) {
+ Logger.e(TAG, "Max animation FPS cannot be less or equal to 0.");
+ return;
+ }
+ this.maxAnimationFps = maxAnimationFps;
+ }
+
+ private Interpolator retrievePulseInterpolator(String interpolatorAnimationType) {
+ switch(interpolatorAnimationType) {
+ case PulseMode.LINEAR:
+ return new LinearInterpolator();
+ case PulseMode.ACCELERATE:
+ return new AccelerateInterpolator();
+ case PulseMode.DECELERATE:
+ return new DecelerateInterpolator();
+ case PulseMode.BOUNCE:
+ return new BounceInterpolator();
+ default:
+ return new DecelerateInterpolator();
}
- this.maxAnimationFps = maxAnimationFps;
}
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java
index d643795c04..03aee9d1f3 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java
@@ -11,6 +11,7 @@ import android.support.annotation.Nullable;
import android.support.annotation.RequiresPermission;
import android.support.annotation.StyleRes;
import android.support.annotation.VisibleForTesting;
+import android.util.Log;
import android.view.WindowManager;
import com.mapbox.android.core.location.LocationEngine;
@@ -654,6 +655,7 @@ public final class LocationComponent {
*
* @param options to update the current style
*/
+ @SuppressLint("MissingPermission")
public void applyStyle(@NonNull final LocationComponentOptions options) {
checkActivationState();
LocationComponent.this.options = options;
@@ -665,6 +667,9 @@ public final class LocationComponent {
locationAnimatorCoordinator.setTrackingAnimationDurationMultiplier(options.trackingAnimationDurationMultiplier());
locationAnimatorCoordinator.setCompassAnimationEnabled(options.compassAnimationEnabled());
locationAnimatorCoordinator.setAccuracyAnimationEnabled(options.accuracyAnimationEnabled());
+ if (options.pulseEnabled()) {
+ locationAnimatorCoordinator.startLocationCirclePulsing(options, mapboxMap);
+ }
updateMapWithOptions(options);
}
}
@@ -1140,6 +1145,10 @@ public final class LocationComponent {
if (locationEngine != null) {
locationEngine.removeLocationUpdates(currentLocationEngineListener);
}
+ if (options.pulseEnabled()) {
+ Log.d(TAG, "onLocationLayerStop: about to stop animation ");
+ locationAnimatorCoordinator.stopPulsingAnimation();
+ }
mapboxMap.removeOnCameraMoveListener(onCameraMoveListener);
mapboxMap.removeOnCameraIdleListener(onCameraIdleListener);
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentConstants.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentConstants.java
index f2158584c7..66c8cdc180 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentConstants.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentConstants.java
@@ -80,6 +80,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 PROPERTY_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";
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java
index a7b83d7d9d..ccb3709120 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java
@@ -11,6 +11,7 @@ import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StyleRes;
+import android.util.Log;
import com.mapbox.android.gestures.AndroidGesturesManager;
import com.mapbox.mapboxsdk.R;
@@ -68,6 +69,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 = 1500;
+
+ /**
+ * Default number of milliseconds which pass after the LocationComponent circle pulse ends and a new pulse begins.
+ */
+ private static final float CIRCLE_PULSING_FREQUENCY_DEFAULT_MS = 1800;
+
+ /**
+ * Default opacity of the LocationComponent circle when it ends a single pulse.
+ */
+ private static final float CIRCLE_PULSING_FINAL_ALPHA_DEFAULT = 0.6f;
+
private float accuracyAlpha;
private int accuracyColor;
private int backgroundDrawableStale;
@@ -112,6 +128,13 @@ public class LocationComponentOptions implements Parcelable {
private float trackingAnimationDurationMultiplier;
private boolean compassAnimationEnabled;
private boolean accuracyAnimationEnabled;
+ private boolean pulsingCircleEnabled;
+ private boolean pulsingCircleFadeEnabled;
+ private int pulseColor;
+ private float pulseSingleDuration;
+ private float pulseFrequency;
+ private float pulseAlpha;
+ private String pulseInterpolator;
public LocationComponentOptions(
float accuracyAlpha,
@@ -145,7 +168,14 @@ public class LocationComponentOptions implements Parcelable {
String layerBelow,
float trackingAnimationDurationMultiplier,
boolean compassAnimationEnabled,
- boolean accuracyAnimationEnabled) {
+ boolean accuracyAnimationEnabled,
+ boolean pulsingCircleEnabled,
+ boolean pulsingCircleFadeEnabled,
+ Integer pulsingCircleColor,
+ float pulsingCircleDuration,
+ float pulsingCircleFrequency,
+ float pulsingCircleAlpha,
+ String pulsingCircleInterpolator) {
this.accuracyAlpha = accuracyAlpha;
this.accuracyColor = accuracyColor;
this.backgroundDrawableStale = backgroundDrawableStale;
@@ -181,6 +211,13 @@ public class LocationComponentOptions implements Parcelable {
this.trackingAnimationDurationMultiplier = trackingAnimationDurationMultiplier;
this.compassAnimationEnabled = compassAnimationEnabled;
this.accuracyAnimationEnabled = accuracyAnimationEnabled;
+ this.pulsingCircleEnabled = pulsingCircleEnabled;
+ this.pulsingCircleFadeEnabled = pulsingCircleFadeEnabled;
+ this.pulseColor = pulsingCircleColor;
+ this.pulseSingleDuration = pulsingCircleDuration;
+ this.pulseFrequency = pulsingCircleFrequency;
+ this.pulseAlpha = pulsingCircleAlpha;
+ this.pulseInterpolator = pulsingCircleInterpolator;
}
/**
@@ -295,6 +332,35 @@ public class LocationComponentOptions implements Parcelable {
R.styleable.mapbox_LocationComponent_mapbox_accuracyAnimationEnabled, true
);
+ builder.pulsingCircleEnabled = typedArray.getBoolean(
+ R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleEnabled, false
+ );
+
+ builder.pulsingCircleFadeEnabled = typedArray.getBoolean(
+ R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleFadeEnabled, true
+ );
+
+ if (typedArray.hasValue(R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleColor)) {
+ builder.pulsingCircleColor(typedArray.getColor(
+ R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleColor,
+ -1));
+ }
+
+ builder.pulsingCircleDuration = typedArray.getFloat(
+ R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleDuration, CIRCLE_PULSING_DURATION_DEFAULT_MS
+ );
+
+ builder.pulsingCircleFrequency = typedArray.getFloat(
+ R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleFrequency, CIRCLE_PULSING_FREQUENCY_DEFAULT_MS
+ );
+
+ builder.pulsingCircleAlpha = typedArray.getFloat(
+ R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleAlpha, CIRCLE_PULSING_FINAL_ALPHA_DEFAULT
+ );
+
+ builder.pulsingCircleInterpolator = typedArray.getString(
+ R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleInterpolator);
+
typedArray.recycle();
return builder.build();
@@ -717,6 +783,71 @@ 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 pulsingCircleEnabled;
+ }
+
+
+ /**
+ * 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 pulsingCircleFadeEnabled() {
+ return pulsingCircleFadeEnabled;
+ }
+
+ /**
+ * 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 number of milliseconds between each pulse of the LocationComponent's circle
+ *
+ * @return the current set length of time between pulses
+ */
+ public float pulseFrequency() {
+ return pulseFrequency;
+ }
+
+ /**
+ * The opacity of the LocationComponent's circle as it pulses.
+ *
+ * @return the current set opacity of the LocationComponent's 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
+ */
+ public String pulseInterpolator() {
+ return pulseInterpolator;
+ }
+
@NonNull
@Override
public String toString() {
@@ -750,7 +881,13 @@ public class LocationComponentOptions implements Parcelable {
+ "trackingInitialMoveThreshold=" + trackingInitialMoveThreshold + ", "
+ "trackingMultiFingerMoveThreshold=" + trackingMultiFingerMoveThreshold + ", "
+ "layerBelow=" + layerBelow
- + "trackingAnimationDurationMultiplier=" + trackingAnimationDurationMultiplier
+ + "enablePulsingCircle=" + pulsingCircleEnabled
+ + "enablePulsingCircleFade=" + pulsingCircleFadeEnabled
+ + "pulseColor=" + pulseColor
+ + "pulseSingleDuration=" + pulseSingleDuration
+ + "pulseFrequency=" + pulseFrequency
+ + "pulseAlpha=" + pulseAlpha
+ + "pulseInterpolator=" + pulseInterpolator
+ "}";
}
@@ -803,7 +940,18 @@ public class LocationComponentOptions implements Parcelable {
== Float.floatToIntBits(that.trackingMultiFingerMoveThreshold()))
&& layerBelow.equals(that.layerBelow))
&& (Float.floatToIntBits(this.trackingAnimationDurationMultiplier)
- == Float.floatToIntBits(that.trackingAnimationDurationMultiplier()));
+ == Float.floatToIntBits(that.trackingAnimationDurationMultiplier()))
+ && (this.pulsingCircleEnabled == that.pulseEnabled())
+ && (this.pulsingCircleFadeEnabled == that.pulseEnabled())
+ && (this.pulseColor == that.pulseColor())
+ && (Float.floatToIntBits(this.pulseSingleDuration)
+ == Float.floatToIntBits(that.pulseSingleDuration()))
+ && (Float.floatToIntBits(this.pulseFrequency)
+ == Float.floatToIntBits(that.pulseFrequency()))
+ && (Float.floatToIntBits(this.pulseAlpha)
+ == Float.floatToIntBits(that.pulseAlpha()))
+ && ((this.pulseInterpolator == null) ? (that.pulseInterpolator() == null)
+ : this.pulseInterpolator.equals(that.pulseInterpolator()));
}
return false;
}
@@ -873,6 +1021,21 @@ public class LocationComponentOptions implements Parcelable {
h$ ^= compassAnimationEnabled ? 1231 : 1237;
h$ *= 1000003;
h$ ^= accuracyAnimationEnabled ? 1231 : 1237;
+ h$ ^= 1000003;
+ h$ ^= pulsingCircleEnabled ? 1231 : 1237;
+ h$ ^= 1000003;
+ h$ ^= pulsingCircleFadeEnabled ? 1231 : 1237;
+ h$ ^= 1000003;
+ h$ ^= pulseColor;
+ h$ *= 1000003;
+ h$ ^= Float.floatToIntBits(pulseSingleDuration);
+ h$ *= 1000003;
+ h$ ^= Float.floatToIntBits(pulseFrequency);
+ h$ *= 1000003;
+ h$ ^= Float.floatToIntBits(pulseAlpha);
+ h$ *= 1000003;
+ h$ ^= (pulseInterpolator == null) ? 0 : pulseInterpolator.hashCode();
+ h$ *= 1000003;
return h$;
}
@@ -912,7 +1075,14 @@ public class LocationComponentOptions implements Parcelable {
in.readString(),
in.readFloat(),
in.readInt() == 1,
- in.readInt() == 1
+ in.readInt() == 1,
+ in.readInt() == 1,
+ in.readInt() == 1,
+ in.readInt() == 0 ? in.readInt() : null,
+ in.readFloat(),
+ in.readFloat(),
+ in.readFloat(),
+ in.readString()
);
}
@@ -1011,6 +1181,18 @@ 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(pulsingCircleFadeEnabled() ? 1 : 0);
+ if (pulseColor() == null) {
+ dest.writeInt(1);
+ } else {
+ dest.writeInt(0);
+ dest.writeInt(pulseColor());
+ }
+ dest.writeFloat(pulseSingleDuration());
+ dest.writeFloat(pulseFrequency());
+ dest.writeFloat(pulseAlpha());
+ dest.writeString(pulseInterpolator());
}
@Override
@@ -1041,6 +1223,18 @@ public class LocationComponentOptions implements Parcelable {
+ locationComponentOptions.elevation() + ". Must be >= 0");
}
+ // TODO: Finish this logic below
+
+ /*Log.d("LocationComponentOptions", "locationComponentOptions.pulseFrequency() = " + locationComponentOptions.pulseFrequency());
+ Log.d("LocationComponentOptions", "locationComponentOptions.pulseSingleDuration() = " + locationComponentOptions.pulseSingleDuration());
+ if (locationComponentOptions.pulseFrequency() < locationComponentOptions.pulseSingleDuration()) {
+
+ throw new IllegalArgumentException("Invalid relationship between the LocationComponent " +
+ "pulsing circle frequency of " + locationComponentOptions.pulseFrequency() + " and duration of " +
+ locationComponentOptions.pulseSingleDuration() + " . The frequency of the pulsing must be >= to the duration " +
+ "of a single pulse.");
+ }*/
+
return locationComponentOptions;
}
@@ -1088,6 +1282,13 @@ public class LocationComponentOptions implements Parcelable {
private Float trackingAnimationDurationMultiplier;
private Boolean compassAnimationEnabled;
private Boolean accuracyAnimationEnabled;
+ private boolean pulsingCircleEnabled;
+ private boolean pulsingCircleFadeEnabled;
+ private int pulsingCircleColor;
+ private float pulsingCircleDuration;
+ private float pulsingCircleFrequency;
+ private float pulsingCircleAlpha;
+ private String pulsingCircleInterpolator;
Builder() {
}
@@ -1125,6 +1326,13 @@ public class LocationComponentOptions implements Parcelable {
this.trackingAnimationDurationMultiplier = source.trackingAnimationDurationMultiplier();
this.compassAnimationEnabled = source.compassAnimationEnabled();
this.accuracyAnimationEnabled = source.accuracyAnimationEnabled();
+ this.pulsingCircleEnabled = source.pulsingCircleEnabled;
+ this.pulsingCircleFadeEnabled = source.pulsingCircleFadeEnabled;
+ this.pulsingCircleColor = source.pulseColor;
+ this.pulsingCircleDuration = source.pulseSingleDuration;
+ this.pulsingCircleFrequency = source.pulseFrequency;
+ this.pulsingCircleAlpha = source.pulseAlpha;
+ this.pulsingCircleInterpolator = source.pulseInterpolator;
}
/**
@@ -1585,11 +1793,82 @@ 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 pulsingCircleEnabled(Boolean pulsingCircleEnabled) {
+ this.pulsingCircleEnabled = pulsingCircleEnabled;
+ 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 pulsingCircleFadeEnabled(Boolean pulsingCircleFadeEnabled) {
+ this.pulsingCircleFadeEnabled = pulsingCircleFadeEnabled;
+ return this;
+ }
+
+ /**
+ * Sets the color of the LocationComponent's pulsing circle.
+ *
+ * @return the current set color of the circle
+ */
+ public LocationComponentOptions.Builder pulsingCircleColor(int pulsingCircleColor) {
+ this.pulsingCircleColor = pulsingCircleColor;
+ 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 pulsingCircleDuration(float pulsingCircleDuration) {
+ this.pulsingCircleDuration = pulsingCircleDuration;
+ return this;
+ }
+
+ /**
+ * Sets the number of milliseconds that are waiting between pulses of the LocationComponent's pulsing circle.
+ *
+ * @return the current set length of time between pulses
+ */
+ public LocationComponentOptions.Builder pulsingCircleFrequency(float pulsingCircleFrequency) {
+ this.pulsingCircleFrequency = pulsingCircleFrequency;
+ return this;
+ }
+
+ /**
+ * Sets the opacity of the LocationComponent's pulsing circle.
+ *
+ * @return the current set opacity of the LocationComponent's circle
+ */
+ public LocationComponentOptions.Builder pulsingCircleAlpha(float pulsingCircleAlpha) {
+ this.pulsingCircleAlpha = pulsingCircleAlpha;
+ return this;
+ }
+
+ /**
+ * Sets the pulsing circle's interpolator animation.
+ *
+ * @return the interpolator animation which the pulsing circle is using
+ */
+ public LocationComponentOptions.Builder pulsingCircleInterpolator(String pulsingCircleInterpolator) {
+ this.pulsingCircleInterpolator = pulsingCircleInterpolator;
+ return this;
+ }
+
@Nullable
LocationComponentOptions autoBuild() {
String missing = "";
@@ -1644,8 +1923,8 @@ public class LocationComponentOptions implements Parcelable {
if (this.trackingMultiFingerMoveThreshold == null) {
missing += " trackingMultiFingerMoveThreshold";
}
- if (this.trackingAnimationDurationMultiplier == null) {
- missing += " trackingAnimationDurationMultiplier";
+ if (this.pulsingCircleInterpolator == null) {
+ missing += " pulseInterpolator";
}
if (!missing.isEmpty()) {
throw new IllegalStateException("Missing required properties:" + missing);
@@ -1682,7 +1961,14 @@ public class LocationComponentOptions implements Parcelable {
this.layerBelow,
this.trackingAnimationDurationMultiplier,
this.compassAnimationEnabled,
- this.accuracyAnimationEnabled);
+ this.accuracyAnimationEnabled,
+ this.pulsingCircleEnabled,
+ this.pulsingCircleFadeEnabled,
+ this.pulsingCircleColor,
+ this.pulsingCircleDuration,
+ this.pulsingCircleFrequency,
+ this.pulsingCircleAlpha,
+ this.pulsingCircleInterpolator);
}
}
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationLayerController.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationLayerController.java
index aa8a82bf6d..5d2ff4d0ee 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationLayerController.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationLayerController.java
@@ -6,6 +6,7 @@ import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
+import android.util.Log;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
@@ -19,6 +20,7 @@ import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -46,6 +48,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.PROPERTY_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.interpolate;
@@ -54,6 +57,9 @@ import static com.mapbox.mapboxsdk.style.expressions.Expression.stop;
import static com.mapbox.mapboxsdk.style.expressions.Expression.zoom;
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.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;
@@ -72,6 +78,8 @@ final class LocationLayerController {
@VisibleForTesting
final Set<String> layerSet = new HashSet<>();
+ private final List<String> layerMap = new ArrayList<>();
+ private final String TAG = "LocationLayerController";
private Feature locationFeature;
private GeoJsonSource locationSource;
@@ -133,6 +141,8 @@ final class LocationLayerController {
styleBearing(options);
styleAccuracy(options.accuracyAlpha(), options.accuracyColor());
styleScaling(options);
+ styleScaling(options);
+ stylePulsingCircle(options);
determineIconsSource(options);
}
@@ -244,6 +254,7 @@ final class LocationLayerController {
addSymbolLayer(BACKGROUND_LAYER, FOREGROUND_LAYER);
addSymbolLayer(SHADOW_LAYER, BACKGROUND_LAYER);
addAccuracyLayer();
+ addPulsingCircleLayerToMap();
}
private void addSymbolLayer(@NonNull String layerId, @NonNull String beforeLayerId) {
@@ -261,6 +272,11 @@ final class LocationLayerController {
layerSet.add(layer.getId());
}
+ private void addPulsingCircleLayerToMap() {
+ Layer pulsingCircleLayer = layerSourceProvider.generatePulsingCircleLayer();
+ addLayerToMap(pulsingCircleLayer, ACCURACY_LAYER);
+ }
+
private void removeLayers() {
for (String layerId : layerSet) {
style.removeLayer(layerId);
@@ -367,6 +383,21 @@ final class LocationLayerController {
}
}
+ private void stylePulsingCircle(LocationComponentOptions options) {
+ if (mapboxMap.getStyle() != null) {
+ if (mapboxMap.getStyle().getLayer(PROPERTY_PULSING_CIRCLE_LAYER) != null) {
+ Log.d(TAG, "stylePulsingCircle: options.pulseEnabled() = " + options.pulseEnabled());
+ // TODO: Swap true for options.pulseEnabled() but figure out why options.pulseEnabled() sometimes returns false
+ setLayerVisibility(PROPERTY_PULSING_CIRCLE_LAYER, true);
+ mapboxMap.getStyle().getLayer(PROPERTY_PULSING_CIRCLE_LAYER).setProperties(
+ circleColor(options.pulseColor()),
+ circleStrokeColor(options.pulseColor()),
+ circleOpacity(options.pulseAlpha())
+ );
+ }
+ }
+ }
+
private void determineIconsSource(LocationComponentOptions options) {
String foregroundIconString = buildIconString(
renderMode == RenderMode.GPS ? options.gpsName() : options.foregroundName(), FOREGROUND_ICON);
@@ -442,6 +473,7 @@ final class LocationLayerController {
new MapboxAnimator.AnimationsValueChangeListener<Float>() {
@Override
public void onNewAnimationValue(Float value) {
+ // TODO: Hide the pulsing circle layer once the accuracy circle is visibile? (i.e. newAnimationValue > 0)
updateAccuracyRadius(value);
}
};
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/PulsingLocationCircleAnimator.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/PulsingLocationCircleAnimator.java
new file mode 100644
index 0000000000..c8f6260fb1
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/PulsingLocationCircleAnimator.java
@@ -0,0 +1,80 @@
+package com.mapbox.mapboxsdk.location;
+
+import android.animation.ValueAnimator;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.util.Log;
+import android.view.animation.Interpolator;
+
+import com.mapbox.mapboxsdk.maps.MapboxMap;
+import com.mapbox.mapboxsdk.style.layers.Layer;
+
+import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_PULSING_CIRCLE_LAYER;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleOpacity;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleRadius;
+
+/**
+ * Manages the logic of the interpolated animation which is applied to the LocationComponent's pulsing circle
+ */
+class PulsingLocationCircleAnimator {
+
+ private static final String TAG = "Mbgl-PulsingLocationCircleAnimator";
+ private static final float PULSING_CIRCLE_RADIUS = 60;
+ private Handler handler;
+ private Runnable runnable;
+ private float opacityCounter;
+
+ public PulsingLocationCircleAnimator() {
+ this.opacityCounter = 0;
+ }
+
+ /**
+ * Start the LocationComponent circle pulse animation
+ *
+ * @param interpolatorToUse the type of Android-system interpolator to use
+ * @param mapboxMap the MapboxMap object which pulsing circle should be shown on
+ * @param locationComponentOptions the stying options of the LocationComponent pulsing circle
+ */
+ public void animatePulsingCircleRadius(@NonNull final Interpolator interpolatorToUse,
+ @NonNull final MapboxMap mapboxMap,
+ @NonNull final LocationComponentOptions locationComponentOptions) {
+ handler = new Handler();
+ runnable = new Runnable() {
+ @Override
+ public void run() {
+ // Check if we are at the end of the points list, if so we want to stop using
+ // the handler.
+ if (mapboxMap.getStyle().getLayer(PROPERTY_PULSING_CIRCLE_LAYER) != null) {
+
+ opacityCounter = 0;
+
+ final Layer pulsingCircleLayer = mapboxMap.getStyle().getLayer(PROPERTY_PULSING_CIRCLE_LAYER);
+ ValueAnimator animator = ValueAnimator.ofFloat(0f, PULSING_CIRCLE_RADIUS);
+ animator.setDuration((long) locationComponentOptions.pulseSingleDuration());
+ animator.setRepeatMode(ValueAnimator.RESTART);
+ animator.setInterpolator(interpolatorToUse);
+ animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ pulsingCircleLayer.setProperties(circleRadius((Float) valueAnimator.getAnimatedValue()));
+ if (locationComponentOptions.pulsingCircleFadeEnabled()) {
+ pulsingCircleLayer.setProperties(circleOpacity(1 - opacityCounter * .01f));
+ opacityCounter++;
+ }
+ }
+ });
+ animator.start();
+ // Once we finish we need to repeat the entire process by executing the
+ // handler again once the ValueAnimator is finished.
+ handler.postDelayed(this, (long) locationComponentOptions.pulseFrequency());
+ }
+ }
+ };
+ handler.post(runnable);
+ }
+
+ public void stopPulsingAnimation() {
+ Log.d(TAG, "stopPulsingAnimation: ");
+ handler.removeCallbacks(runnable);
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/Utils.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/Utils.java
index 49f9a7c43c..f179586782 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/Utils.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/Utils.java
@@ -56,6 +56,14 @@ public final class Utils {
return (float) (location.getAccuracy() * (1 / metersPerPixel));
}
+ static float calculatePulsingCircleRadius(@NonNull MapboxMap mapboxMap, @Nullable Location location) {
+ if (location == null) {
+ return 0;
+ }
+ double metersPerPixel = mapboxMap.getProjection().getMetersPerPixelAtLatitude(location.getLatitude());
+ return (float) (location.getAccuracy() * (1 / metersPerPixel));
+ }
+
static boolean immediateAnimation(@NonNull Projection projection, @NonNull LatLng current, @NonNull LatLng target) {
double metersPerPixel = projection.getMetersPerPixelAtLatitude((current.getLatitude() + target.getLatitude()) / 2);
double distance = current.distanceTo(target);
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/modes/PulseMode.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/modes/PulseMode.java
new file mode 100644
index 0000000000..1f905716ea
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/modes/PulseMode.java
@@ -0,0 +1,46 @@
+package com.mapbox.mapboxsdk.location.modes;
+
+import android.support.annotation.StringDef;
+
+import com.mapbox.mapboxsdk.location.LocationComponentOptions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public final class PulseMode {
+
+ private PulseMode() {
+ // Class should not be initialized
+ }
+
+ /**
+ * An interpolator defines the rate of change of an animation.
+ *
+ * One of these constants should be used with {@link LocationComponentOptions#pulseInterpolator}.
+ *
+ */
+ @StringDef( {LINEAR, ACCELERATE, DECELERATE, BOUNCE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Mode {
+ }
+
+ /**
+ * An interpolator where the rate of change is constant.
+ */
+ public static final String LINEAR = "linear";
+
+ /**
+ * An interpolator where the rate of change starts out slowly and and then accelerates.
+ */
+ public static final String ACCELERATE = "accelerate";
+
+ /**
+ * An interpolator where the rate of change starts out quickly and and then decelerates.
+ */
+ public static final String DECELERATE = "decelerate";
+
+ /**
+ * An interpolator where the change bounces at the end.
+ */
+ public static final String BOUNCE = "bounce";
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml
index 58109257f2..39b6813c53 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml
@@ -12,7 +12,7 @@
<public name="mapbox_apiBaseUrl" type="attr" />
<public name="mapbox_localIdeographFontFamily" type="attr" />
<public name="mapbox_cross_source_collisions" type="attr" />
- <public name="mapbox_pixelRatio" type="float" />
+ <public name="mapbox_pixelRatio" type="float" />
<!--Camera-->
<public name="mapbox_cameraTargetLng" type="attr" />
@@ -105,42 +105,42 @@
<!-- Exposed colors -->
<public name="mapbox_blue" type="color" />
- <public name="mapbox_LocationComponent" type="style"/>
+ <public name="mapbox_LocationComponent" type="style" />
- <public name="mapbox_foregroundDrawable" format="reference" type="attr"/>
- <public name="mapbox_foregroundTintColor" format="color" type="attr"/>
- <public name="mapbox_backgroundDrawable" format="reference" type="attr"/>
- <public name="mapbox_backgroundTintColor" format="color" type="attr"/>
- <public name="mapbox_bearingDrawable" format="reference" type="attr"/>
- <public name="mapbox_bearingTintColor" format="color" type="attr"/>
- <public name="mapbox_navigationDrawable" format="reference" type="attr"/>
+ <public name="mapbox_foregroundDrawable" format="reference" type="attr" />
+ <public name="mapbox_foregroundTintColor" format="color" type="attr" />
+ <public name="mapbox_backgroundDrawable" format="reference" type="attr" />
+ <public name="mapbox_backgroundTintColor" format="color" type="attr" />
+ <public name="mapbox_bearingDrawable" format="reference" type="attr" />
+ <public name="mapbox_bearingTintColor" format="color" type="attr" />
+ <public name="mapbox_navigationDrawable" format="reference" type="attr" />
- <public name="mapbox_foregroundDrawableStale" format="reference" type="attr"/>
- <public name="mapbox_foregroundStaleTintColor" format="color" type="attr"/>
- <public name="mapbox_backgroundDrawableStale" format="reference" type="attr"/>
- <public name="mapbox_backgroundStaleTintColor" format="color" type="attr"/>
+ <public name="mapbox_foregroundDrawableStale" format="reference" type="attr" />
+ <public name="mapbox_foregroundStaleTintColor" format="color" type="attr" />
+ <public name="mapbox_backgroundDrawableStale" format="reference" type="attr" />
+ <public name="mapbox_backgroundStaleTintColor" format="color" type="attr" />
- <public name="mapbox_accuracyAlpha" format="float" type="attr"/>
- <public name="mapbox_accuracyColor" format="color" type="attr"/>
+ <public name="mapbox_accuracyAlpha" format="float" type="attr" />
+ <public name="mapbox_accuracyColor" format="color" type="attr" />
- <public name="mapbox_elevation" format="dimension" type="attr"/>
+ <public name="mapbox_elevation" format="dimension" type="attr" />
- <public name="mapbox_enableStaleState" format="boolean" type="attr"/>
- <public name="mapbox_staleStateTimeout" format="integer" min="0" type="attr"/>
+ <public name="mapbox_enableStaleState" format="boolean" type="attr" />
+ <public name="mapbox_staleStateTimeout" format="integer" min="0" type="attr" />
<!-- Location icon padding -->
- <public name="mapbox_iconPaddingLeft" format="integer" type="attr"/>
- <public name="mapbox_iconPaddingTop" format="integer" type="attr"/>
- <public name="mapbox_iconPaddingRight" format="integer" type="attr"/>
- <public name="mapbox_iconPaddingBottom" format="integer" type="attr"/>
+ <public name="mapbox_iconPaddingLeft" format="integer" type="attr" />
+ <public name="mapbox_iconPaddingTop" format="integer" type="attr" />
+ <public name="mapbox_iconPaddingRight" format="integer" type="attr" />
+ <public name="mapbox_iconPaddingBottom" format="integer" type="attr" />
<!-- Icon scale based on map zoom levels -->
- <public name="mapbox_maxZoomIconScale" format="float" type="attr"/>
- <public name="mapbox_minZoomIconScale" format="float" type="attr"/>
+ <public name="mapbox_maxZoomIconScale" format="float" type="attr" />
+ <public name="mapbox_minZoomIconScale" format="float" type="attr" />
<!-- Camera tracking settings -->
- <public name="mapbox_trackingInitialMoveThreshold" format="float" type="attr"/>
- <public name="mapbox_trackingMultiFingerMoveThreshold" format="float" type="attr"/>
+ <public name="mapbox_trackingInitialMoveThreshold" format="float" type="attr" />
+ <public name="mapbox_trackingMultiFingerMoveThreshold" format="float" type="attr" />
<!-- Animation duration multiplier -->
<public name="mapbox_trackingAnimationDurationMultiplier" format="float" type="attr" />
@@ -150,4 +150,13 @@
<!-- Accuracy animation-->
<public name="mapbox_accuracyAnimationEnabled" format="boolean" 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_pulsingLocationCircleFrequency" format="float" type="attr" />
+ <public name="mapbox_pulsingLocationCircleAlpha" format="float" type="attr" />
+ <public name="mapbox_pulsingLocationCircleInterpolator" format="string" type="attr" />
</resources>
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml
index 2eeb5057f2..f47327c8a9 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml
@@ -176,5 +176,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_pulsingLocationCircleFrequency" format="float" />
+ <attr name="mapbox_pulsingLocationCircleAlpha" format="float" />
+ <attr name="mapbox_pulsingLocationCircleInterpolator" format="string" />
+
</declare-styleable>
</resources>
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml
index c6c2d3fc7b..8cb784eb58 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml
@@ -33,5 +33,11 @@
<item name="mapbox_trackingInitialMoveThreshold">@dimen/mapbox_locationComponentTrackingInitialMoveThreshold</item>
<item name="mapbox_trackingMultiFingerMoveThreshold">@dimen/mapbox_locationComponentTrackingMultiFingerMoveThreshold</item>
+ <!-- Location pulsing circle -->
+ <item name="mapbox_pulsingLocationCircleColor">@color/mapbox_location_layer_blue</item>
+ <item name="mapbox_pulsingLocationCircleDuration">2.5</item>
+ <item name="mapbox_pulsingLocationCircleFrequency">2</item>
+ <item name="mapbox_pulsingLocationCircleAlpha">0.4</item>
+ <item name="mapbox_pulsingLocationCircleInterpolator">linear</item>
</style>
</resources> \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
index 017fe3ddca..6e048b49f2 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
@@ -865,6 +865,17 @@
android:value=".activity.FeatureOverviewActivity" />
</activity>
<activity
+ android:name=".activity.location.LocationPulsingCircleActivity"
+ android:description="@string/description_location_pulsing_circle"
+ android:label="@string/activity_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"
android:label="@string/activity_location_fragment">
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MapboxApplication.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MapboxApplication.java
index d5cff301db..d07b8a8245 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MapboxApplication.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MapboxApplication.java
@@ -52,7 +52,7 @@ public class MapboxApplication extends Application {
private void initializeLogger() {
Logger.setLoggerDefinition(new TimberLogger());
- if (BuildConfig.DEBUG) {
+ if (BuildConfig.DEBUG) {contro
Timber.plant(new DebugTree());
}
}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/location/LocationPulsingCircleActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/location/LocationPulsingCircleActivity.java
new file mode 100644
index 0000000000..1694b7de5a
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/location/LocationPulsingCircleActivity.java
@@ -0,0 +1,324 @@
+package com.mapbox.mapboxsdk.testapp.activity.location;
+
+import android.annotation.SuppressLint;
+import android.graphics.Color;
+import android.location.Location;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.ListPopupWindow;
+import android.view.Menu;
+import android.view.MenuItem;
+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.LocationComponentOptions;
+import com.mapbox.mapboxsdk.location.modes.CameraMode;
+import com.mapbox.mapboxsdk.location.modes.PulseMode;
+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;
+
+public class LocationPulsingCircleActivity extends AppCompatActivity implements OnMapReadyCallback {
+
+ private MapView mapView;
+ private Button pulsingCircleFrequencyButton;
+ private Button pulsingCircleColorButton;
+
+ private PermissionsManager permissionsManager;
+
+ private LocationComponent locationComponent;
+ private MapboxMap mapboxMap;
+ private boolean defaultStyle = false;
+ private float currentPulseFrequency;
+
+ private static final String SAVED_STATE_LOCATION = "saved_state_location";
+
+ // Make sure that the frequency value is equal to or larger than the duration value.
+ private static final float DEFAULT_LOCATION_CIRCLE_PULSE_FREQUENCY = 1700;
+ private static final float DEFAULT_LOCATION_CIRCLE_PULSE_DURATION = 1700;
+
+ private static final float DEFAULT_LOCATION_CIRCLE_PULSE_ALPHA = .4f;
+ private static final String DEFAULT_LOCATION_CIRCLE_INTERPOLATOR_PULSE_MODE = PulseMode.DECELERATE;
+ private static final boolean DEFAULT_LOCATION_CIRCLE_PULSE_FADE_MODE = false;
+
+ private static int DEFAULT_LOCATION_CIRCLE_PULSE_COLOR;
+
+ private Location lastLocation;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_location_layer_pulsing_circle);
+
+ DEFAULT_LOCATION_CIRCLE_PULSE_COLOR = ContextCompat.getColor(this, R.color.mapbox_location_layer_blue);
+
+ mapView = findViewById(R.id.mapView);
+
+ pulsingCircleFrequencyButton = findViewById(R.id.button_location_circle_frequency);
+ pulsingCircleFrequencyButton.setText(String.format("%sms",
+ String.valueOf(DEFAULT_LOCATION_CIRCLE_PULSE_FREQUENCY)));
+ pulsingCircleFrequencyButton.setOnClickListener(v -> {
+ if (locationComponent == null) {
+ return;
+ }
+ showFrequencyListDialog();
+ });
+
+ pulsingCircleColorButton = findViewById(R.id.button_location_circle_color);
+ pulsingCircleColorButton.setOnClickListener(v -> {
+ if (locationComponent == null) {
+ return;
+ }
+ showColorListDialog();
+ });
+
+
+ if (savedInstanceState != null) {
+ lastLocation = savedInstanceState.getParcelable(SAVED_STATE_LOCATION);
+ }
+
+ mapView.onCreate(savedInstanceState);
+
+ if (PermissionsManager.areLocationPermissionsGranted(this)) {
+ mapView.getMapAsync(this);
+ } else {
+ permissionsManager = new PermissionsManager(new PermissionsListener() {
+ @Override
+ public void onExplanationNeeded(List<String> permissionsToExplain) {
+ Toast.makeText(LocationPulsingCircleActivity.this, "You need to accept location permissions.",
+ Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onPermissionResult(boolean granted) {
+ if (granted) {
+ mapView.getMapAsync(LocationPulsingCircleActivity.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);
+ }
+
+ @SuppressLint("MissingPermission")
+ @Override
+ public void onMapReady(@NonNull MapboxMap mapboxMap) {
+ this.mapboxMap = mapboxMap;
+
+ mapboxMap.setStyle(Style.MAPBOX_STREETS, style -> {
+
+ locationComponent = mapboxMap.getLocationComponent();
+ locationComponent.activateLocationComponent(this, style, true,
+ new LocationEngineRequest.Builder(750)
+ .setFastestInterval(750)
+ .setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
+ .build(),
+ buildLocationComponentOptions(
+ "waterway-label",
+ DEFAULT_LOCATION_CIRCLE_PULSE_COLOR,
+ DEFAULT_LOCATION_CIRCLE_PULSE_ALPHA,
+ DEFAULT_LOCATION_CIRCLE_PULSE_DURATION,
+ DEFAULT_LOCATION_CIRCLE_PULSE_FREQUENCY));
+
+ locationComponent.setLocationComponentEnabled(true);
+ locationComponent.setCameraMode(CameraMode.TRACKING);
+ locationComponent.forceLocationUpdate(lastLocation);
+ });
+ }
+
+ private LocationComponentOptions buildLocationComponentOptions(@Nullable String idOfLayerBelow,
+ @Nullable int pulsingCircleColor,
+ @Nullable float pulsingCircleAlpha,
+ @Nullable float pulsingCircleDuration,
+ @Nullable float pulsingCircleFrequency) {
+ currentPulseFrequency = pulsingCircleFrequency;
+ return LocationComponentOptions.builder(this)
+ .layerBelow(idOfLayerBelow)
+ .pulsingCircleEnabled(true)
+ .pulsingCircleFadeEnabled(DEFAULT_LOCATION_CIRCLE_PULSE_FADE_MODE)
+ .pulsingCircleInterpolator(DEFAULT_LOCATION_CIRCLE_INTERPOLATOR_PULSE_MODE)
+ .pulsingCircleColor(pulsingCircleColor)
+ .pulsingCircleAlpha(pulsingCircleAlpha)
+ .pulsingCircleDuration(pulsingCircleDuration)
+ .pulsingCircleFrequency(pulsingCircleFrequency)
+ .build();
+ }
+
+ @SuppressLint("MissingPermission")
+ private void setNewLocationComponentOptions(@Nullable float newPulsingFrequency,
+ @Nullable int newPulsingColor) {
+ if (mapboxMap.getStyle() != null) {
+ locationComponent.activateLocationComponent(this, mapboxMap.getStyle(),
+ true,
+ new LocationEngineRequest.Builder(750)
+ .setFastestInterval(750)
+ .setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
+ .build(), buildLocationComponentOptions("waterway-label",
+ newPulsingColor, DEFAULT_LOCATION_CIRCLE_PULSE_ALPHA,
+ DEFAULT_LOCATION_CIRCLE_PULSE_DURATION, newPulsingFrequency));
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.menu_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) {
+ toggleMapStyle();
+ 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_component_throttling_enabled) {
+ locationComponent.setMaxAnimationFps(5);
+ } else if (id == R.id.action_component_throttling_disabled) {
+ locationComponent.setMaxAnimationFps(Integer.MAX_VALUE);
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void toggleMapStyle() {
+ if (locationComponent == null) {
+ return;
+ }
+ String styleUrl = Style.DARK.equals(mapboxMap.getStyle().getUrl()) ? Style.LIGHT : Style.DARK;
+ mapboxMap.setStyle(new Style.Builder().fromUrl(styleUrl));
+ }
+
+ @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();
+ }
+
+ private void showFrequencyListDialog() {
+ List<String> modes = new ArrayList<>();
+ modes.add("400ms");
+ modes.add("1000ms");
+ modes.add("2000ms");
+ ArrayAdapter<String> profileAdapter = new ArrayAdapter<>(this,
+ android.R.layout.simple_list_item_1, modes);
+ ListPopupWindow listPopup = new ListPopupWindow(this);
+ listPopup.setAdapter(profileAdapter);
+ listPopup.setAnchorView(pulsingCircleFrequencyButton);
+ listPopup.setOnItemClickListener((parent, itemView, position, id) -> {
+ String selectedMode = modes.get(position);
+ pulsingCircleFrequencyButton.setText(selectedMode);
+ if (selectedMode.contentEquals(String.format("%sms",
+ String.valueOf(DEFAULT_LOCATION_CIRCLE_PULSE_FREQUENCY)))) {
+ setNewLocationComponentOptions(DEFAULT_LOCATION_CIRCLE_PULSE_FREQUENCY, Color.BLUE);
+ } else if (selectedMode.contentEquals("1000ms")) {
+ setNewLocationComponentOptions(1000, Color.BLUE);
+ } else if (selectedMode.contentEquals("3000ms")) {
+ setNewLocationComponentOptions(3000, Color.BLUE);
+ }
+ listPopup.dismiss();
+ });
+ listPopup.show();
+ }
+
+
+ private void showColorListDialog() {
+ List<String> trackingTypes = new ArrayList<>();
+ trackingTypes.add("Blue");
+ trackingTypes.add("Red");
+ trackingTypes.add("Green");
+ trackingTypes.add("Yellow");
+ 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")) {
+ setNewLocationComponentOptions(currentPulseFrequency, Color.BLUE);
+ } else if (selectedTrackingType.contentEquals("Red")) {
+ setNewLocationComponentOptions(currentPulseFrequency, Color.RED);
+ } else if (selectedTrackingType.contentEquals("Green")) {
+ setNewLocationComponentOptions(currentPulseFrequency, Color.GREEN);
+ } else if (selectedTrackingType.contentEquals("Yellow")) {
+ setNewLocationComponentOptions(currentPulseFrequency, Color.YELLOW);
+ }
+ listPopup.dismiss();
+ });
+ listPopup.show();
+ }
+} \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_location_layer_pulsing_circle.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_location_layer_pulsing_circle.xml
new file mode 100644
index 0000000000..36ab53af63
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_location_layer_pulsing_circle.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.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="Frequency:"
+ android:textColor="@color/white"
+ android:textSize="18sp"
+ android:textStyle="bold" />
+
+ <Button
+ android:id="@+id/button_location_circle_frequency"
+ 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>
+
+</android.support.constraint.ConstraintLayout> \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_location_pulsing_circle_styling.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_location_pulsing_circle_styling.xml
new file mode 100644
index 0000000000..cdb0ad5392
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_location_pulsing_circle_styling.xml
@@ -0,0 +1,28 @@
+<?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_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_gestures_management_enabled"
+ android:title="Enable Gestures Management"
+ app:showAsAction="never"/>
+
+ <item android:id="@+id/action_gestures_management_disabled"
+ android:title="Disable Gestures Management"
+ app:showAsAction="never"/>
+
+ <item android:id="@+id/action_component_throttling_enabled"
+ android:title="Enable animation throttling (5 FPS)"
+ app:showAsAction="never"/>
+
+ <item android:id="@+id/action_component_throttling_disabled"
+ android:title="Disable animation throttling"
+ app:showAsAction="never"/>
+</menu> \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml
index 778805b3b3..edf6e5166c 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml
@@ -75,6 +75,7 @@
<string name="description_draggable_marker">Click to add a marker, long-click to drag</string>
<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_pulsing_circle">Showcases the LocationComponent pulsing circle</string>
<string name="description_location_fragment">Uses LocationComponent in a Fragment</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>
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml
index 12c82bf21a..a76cfea05d 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml
@@ -75,6 +75,7 @@
<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_location_pulsing_circle">Location 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>