fix(android): Add option to move symbol location indicator bearing down the image stack (#4408)
This commit is contained in:
@@ -133,6 +133,7 @@ public class LocationComponentOptions implements Parcelable {
|
||||
private RectF trackingMultiFingerProtectedMoveArea;
|
||||
private String layerAbove;
|
||||
private String layerBelow;
|
||||
private boolean bearingOnTop;
|
||||
private float trackingAnimationDurationMultiplier;
|
||||
private boolean compassAnimationEnabled;
|
||||
private boolean accuracyAnimationEnabled;
|
||||
@@ -177,6 +178,7 @@ public class LocationComponentOptions implements Parcelable {
|
||||
RectF trackingMultiFingerProtectedMoveArea,
|
||||
String layerAbove,
|
||||
String layerBelow,
|
||||
boolean bearingOnTop,
|
||||
float trackingAnimationDurationMultiplier,
|
||||
boolean compassAnimationEnabled,
|
||||
boolean accuracyAnimationEnabled,
|
||||
@@ -221,6 +223,7 @@ public class LocationComponentOptions implements Parcelable {
|
||||
this.trackingMultiFingerProtectedMoveArea = trackingMultiFingerProtectedMoveArea;
|
||||
this.layerAbove = layerAbove;
|
||||
this.layerBelow = layerBelow;
|
||||
this.bearingOnTop = bearingOnTop;
|
||||
this.trackingAnimationDurationMultiplier = trackingAnimationDurationMultiplier;
|
||||
this.compassAnimationEnabled = compassAnimationEnabled;
|
||||
this.accuracyAnimationEnabled = accuracyAnimationEnabled;
|
||||
@@ -327,6 +330,9 @@ public class LocationComponentOptions implements Parcelable {
|
||||
builder.layerBelow(
|
||||
typedArray.getString(R.styleable.maplibre_LocationComponent_maplibre_layer_below));
|
||||
|
||||
builder.bearingOnTop(
|
||||
typedArray.getBoolean(R.styleable.maplibre_LocationComponent_maplibre_bearing_on_top, true));
|
||||
|
||||
float minScale = typedArray.getFloat(
|
||||
R.styleable.maplibre_LocationComponent_maplibre_minZoomIconScale, MIN_ZOOM_ICON_SCALE_DEFAULT);
|
||||
float maxScale = typedArray.getFloat(
|
||||
@@ -798,6 +804,15 @@ public class LocationComponentOptions implements Parcelable {
|
||||
return layerBelow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the bearing icon is rendered on top of the foreground icon in the location layer stack.
|
||||
*
|
||||
* @return true if the bearing icon is rendered above the foreground icon, false otherwise.
|
||||
*/
|
||||
public boolean bearingOnTop() {
|
||||
return bearingOnTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tracking animation duration multiplier.
|
||||
*
|
||||
@@ -927,6 +942,7 @@ public class LocationComponentOptions implements Parcelable {
|
||||
+ "trackingMultiFingerProtectedMoveArea=" + trackingMultiFingerProtectedMoveArea + ", "
|
||||
+ "layerAbove=" + layerAbove
|
||||
+ "layerBelow=" + layerBelow
|
||||
+ "bearingOnTop=" + bearingOnTop
|
||||
+ "trackingAnimationDurationMultiplier=" + trackingAnimationDurationMultiplier
|
||||
+ "pulseEnabled=" + pulseEnabled
|
||||
+ "pulseFadeEnabled=" + pulseFadeEnabled
|
||||
@@ -1057,6 +1073,10 @@ public class LocationComponentOptions implements Parcelable {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bearingOnTop != options.bearingOnTop) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pulseEnabled != options.pulseEnabled) {
|
||||
return false;
|
||||
}
|
||||
@@ -1120,6 +1140,7 @@ public class LocationComponentOptions implements Parcelable {
|
||||
? trackingMultiFingerProtectedMoveArea.hashCode() : 0);
|
||||
result = 31 * result + (layerAbove != null ? layerAbove.hashCode() : 0);
|
||||
result = 31 * result + (layerBelow != null ? layerBelow.hashCode() : 0);
|
||||
result = 31 * result + (bearingOnTop ? 1 : 0);
|
||||
result = 31 * result + (trackingAnimationDurationMultiplier != +0.0f
|
||||
? Float.floatToIntBits(trackingAnimationDurationMultiplier) : 0);
|
||||
result = 31 * result + (compassAnimationEnabled ? 1 : 0);
|
||||
@@ -1171,6 +1192,7 @@ public class LocationComponentOptions implements Parcelable {
|
||||
dest.writeParcelable(this.trackingMultiFingerProtectedMoveArea, flags);
|
||||
dest.writeString(this.layerAbove);
|
||||
dest.writeString(this.layerBelow);
|
||||
dest.writeByte(this.bearingOnTop ? (byte) 1 : (byte) 0);
|
||||
dest.writeFloat(this.trackingAnimationDurationMultiplier);
|
||||
dest.writeByte(this.compassAnimationEnabled ? (byte) 1 : (byte) 0);
|
||||
dest.writeByte(this.accuracyAnimationEnabled ? (byte) 1 : (byte) 0);
|
||||
@@ -1214,6 +1236,7 @@ public class LocationComponentOptions implements Parcelable {
|
||||
this.trackingMultiFingerProtectedMoveArea = in.readParcelable(RectF.class.getClassLoader());
|
||||
this.layerAbove = in.readString();
|
||||
this.layerBelow = in.readString();
|
||||
this.bearingOnTop = in.readByte() != 0;
|
||||
this.trackingAnimationDurationMultiplier = in.readFloat();
|
||||
this.compassAnimationEnabled = in.readByte() != 0;
|
||||
this.accuracyAnimationEnabled = in.readByte() != 0;
|
||||
@@ -1338,6 +1361,7 @@ public class LocationComponentOptions implements Parcelable {
|
||||
private RectF trackingMultiFingerProtectedMoveArea;
|
||||
private String layerAbove;
|
||||
private String layerBelow;
|
||||
private boolean bearingOnTop;
|
||||
private Float trackingAnimationDurationMultiplier;
|
||||
private Boolean compassAnimationEnabled;
|
||||
private Boolean accuracyAnimationEnabled;
|
||||
@@ -1385,6 +1409,7 @@ public class LocationComponentOptions implements Parcelable {
|
||||
this.trackingMultiFingerProtectedMoveArea = source.trackingMultiFingerProtectedMoveArea();
|
||||
this.layerAbove = source.layerAbove();
|
||||
this.layerBelow = source.layerBelow();
|
||||
this.bearingOnTop = source.bearingOnTop();
|
||||
this.trackingAnimationDurationMultiplier = source.trackingAnimationDurationMultiplier();
|
||||
this.compassAnimationEnabled = source.compassAnimationEnabled();
|
||||
this.accuracyAnimationEnabled = source.accuracyAnimationEnabled();
|
||||
@@ -1866,6 +1891,19 @@ public class LocationComponentOptions implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the bearing icon is rendered on top of the foreground icon in the location
|
||||
* layer stack. When true, the bearing layer is placed above the foreground layer; when false,
|
||||
* it is rendered below.
|
||||
*
|
||||
* @param bearingOnTop true to render the bearing icon above the foreground icon, false for below.
|
||||
*/
|
||||
@NonNull
|
||||
public LocationComponentOptions.Builder bearingOnTop(boolean bearingOnTop) {
|
||||
this.bearingOnTop = bearingOnTop;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tracking animation duration multiplier.
|
||||
*
|
||||
@@ -2065,6 +2103,7 @@ public class LocationComponentOptions implements Parcelable {
|
||||
this.trackingMultiFingerProtectedMoveArea,
|
||||
this.layerAbove,
|
||||
this.layerBelow,
|
||||
this.bearingOnTop,
|
||||
this.trackingAnimationDurationMultiplier,
|
||||
this.compassAnimationEnabled,
|
||||
this.accuracyAnimationEnabled,
|
||||
|
||||
@@ -17,22 +17,28 @@ class LocationComponentPositionManager {
|
||||
@Nullable
|
||||
private String layerBelow;
|
||||
|
||||
LocationComponentPositionManager(@NonNull Style style, @Nullable String layerAbove, @Nullable String layerBelow) {
|
||||
public boolean bearingOnTop;
|
||||
|
||||
LocationComponentPositionManager(@NonNull Style style, @Nullable String layerAbove,
|
||||
@Nullable String layerBelow, Boolean bearingOnTop) {
|
||||
this.style = style;
|
||||
this.layerAbove = layerAbove;
|
||||
this.layerBelow = layerBelow;
|
||||
this.bearingOnTop = bearingOnTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true whenever layer above/below configuration has changed and requires re-layout.
|
||||
*/
|
||||
boolean update(@Nullable String layerAbove, @Nullable String layerBelow) {
|
||||
boolean update(@Nullable String layerAbove, @Nullable String layerBelow, boolean bearingOnTop) {
|
||||
boolean requiresUpdate =
|
||||
!(this.layerAbove == layerAbove || (this.layerAbove != null && this.layerAbove.equals(layerAbove)))
|
||||
|| !(this.layerBelow == layerBelow || (this.layerBelow != null && this.layerBelow.equals(layerBelow)));
|
||||
|| !(this.layerBelow == layerBelow || (this.layerBelow != null && this.layerBelow.equals(layerBelow)))
|
||||
|| (this.bearingOnTop != bearingOnTop);
|
||||
|
||||
this.layerAbove = layerAbove;
|
||||
this.layerBelow = layerBelow;
|
||||
this.bearingOnTop = bearingOnTop;
|
||||
return requiresUpdate;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,8 @@ final class LocationLayerController {
|
||||
}
|
||||
|
||||
void initializeComponents(Style style, LocationComponentOptions options) {
|
||||
this.positionManager = new LocationComponentPositionManager(style, options.layerAbove(), options.layerBelow());
|
||||
this.positionManager = new LocationComponentPositionManager(style, options.layerAbove(),
|
||||
options.layerBelow(), options.bearingOnTop());
|
||||
locationLayerRenderer.initializeComponents(style);
|
||||
locationLayerRenderer.addLayers(positionManager);
|
||||
applyStyle(options);
|
||||
@@ -87,7 +88,7 @@ final class LocationLayerController {
|
||||
}
|
||||
|
||||
void applyStyle(@NonNull LocationComponentOptions options) {
|
||||
if (positionManager.update(options.layerAbove(), options.layerBelow())) {
|
||||
if (positionManager.update(options.layerAbove(), options.layerBelow(), options.bearingOnTop())) {
|
||||
locationLayerRenderer.removeLayers();
|
||||
locationLayerRenderer.addLayers(positionManager);
|
||||
if (isHidden) {
|
||||
|
||||
@@ -83,15 +83,29 @@ final class SymbolLocationLayerRenderer implements LocationLayerRenderer {
|
||||
|
||||
@Override
|
||||
public void addLayers(LocationComponentPositionManager positionManager) {
|
||||
// positions the top-most reference layer
|
||||
Layer layer = layerSourceProvider.generateLayer(BEARING_LAYER);
|
||||
positionManager.addLayerToMap(layer);
|
||||
layerSet.add(layer.getId());
|
||||
|
||||
// adds remaining layers while keeping the order
|
||||
addSymbolLayer(FOREGROUND_LAYER, BEARING_LAYER);
|
||||
addSymbolLayer(BACKGROUND_LAYER, FOREGROUND_LAYER);
|
||||
addSymbolLayer(SHADOW_LAYER, BACKGROUND_LAYER);
|
||||
if (positionManager.bearingOnTop) {
|
||||
// positions the top-most reference layer
|
||||
Layer layer = layerSourceProvider.generateLayer(BEARING_LAYER);
|
||||
positionManager.addLayerToMap(layer);
|
||||
layerSet.add(layer.getId());
|
||||
|
||||
// adds remaining layers while keeping the order
|
||||
addSymbolLayer(FOREGROUND_LAYER, BEARING_LAYER);
|
||||
addSymbolLayer(BACKGROUND_LAYER, FOREGROUND_LAYER);
|
||||
addSymbolLayer(SHADOW_LAYER, BACKGROUND_LAYER);
|
||||
} else {
|
||||
// positions the top-most reference layer
|
||||
Layer layer = layerSourceProvider.generateLayer(FOREGROUND_LAYER);
|
||||
positionManager.addLayerToMap(layer);
|
||||
layerSet.add(layer.getId());
|
||||
|
||||
// adds remaining layers while keeping the order
|
||||
addSymbolLayer(BACKGROUND_LAYER, FOREGROUND_LAYER);
|
||||
addSymbolLayer(BEARING_LAYER, BACKGROUND_LAYER);
|
||||
addSymbolLayer(SHADOW_LAYER, BEARING_LAYER);
|
||||
}
|
||||
|
||||
addAccuracyLayer();
|
||||
addPulsingCircleLayerToMap();
|
||||
}
|
||||
|
||||
@@ -181,6 +181,7 @@
|
||||
<!-- Map layer configuration -->
|
||||
<attr name="maplibre_layer_above" format="string"/>
|
||||
<attr name="maplibre_layer_below" format="string"/>
|
||||
<attr name="maplibre_bearing_on_top" format="boolean"/>
|
||||
|
||||
<!-- Icon scale based on map zoom levels -->
|
||||
<attr name="maplibre_maxZoomIconScale" format="float"/>
|
||||
|
||||
@@ -26,70 +26,91 @@ class LocationComponentPositionManagerTest : BaseTest() {
|
||||
|
||||
@Test
|
||||
fun update_noChange_null() {
|
||||
val positionManager = LocationComponentPositionManager(style, null, null)
|
||||
val requiresUpdate = positionManager.update(null, null)
|
||||
val positionManager = LocationComponentPositionManager(style, null, null, false)
|
||||
val requiresUpdate = positionManager.update(null, null, false)
|
||||
assertFalse(requiresUpdate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun update_noChange_above() {
|
||||
val positionManager = LocationComponentPositionManager(style, "above", null)
|
||||
val requiresUpdate = positionManager.update("above", null)
|
||||
val positionManager = LocationComponentPositionManager(style, "above", null, false)
|
||||
val requiresUpdate = positionManager.update("above", null, false)
|
||||
assertFalse(requiresUpdate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun update_noChange_below() {
|
||||
val positionManager = LocationComponentPositionManager(style, null, "below")
|
||||
val requiresUpdate = positionManager.update(null, "below")
|
||||
val positionManager = LocationComponentPositionManager(style, null, "below", false)
|
||||
val requiresUpdate = positionManager.update(null, "below", false)
|
||||
assertFalse(requiresUpdate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun update_noChange_bearingOnTop() {
|
||||
val positionManager = LocationComponentPositionManager(style, null, null, true)
|
||||
val requiresUpdate = positionManager.update(null, null, true)
|
||||
assertFalse(requiresUpdate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun update_fromNull_above() {
|
||||
val positionManager = LocationComponentPositionManager(style, null, null)
|
||||
val requiresUpdate = positionManager.update("above", null)
|
||||
val positionManager = LocationComponentPositionManager(style, null, null, false)
|
||||
val requiresUpdate = positionManager.update("above", null, false)
|
||||
assertTrue(requiresUpdate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun update_fromNull_below() {
|
||||
val positionManager = LocationComponentPositionManager(style, null, null)
|
||||
val requiresUpdate = positionManager.update(null, "below")
|
||||
val positionManager = LocationComponentPositionManager(style, null, null, false)
|
||||
val requiresUpdate = positionManager.update(null, "below", false)
|
||||
assertTrue(requiresUpdate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun update_toNull_above() {
|
||||
val positionManager = LocationComponentPositionManager(style, "above", null)
|
||||
val requiresUpdate = positionManager.update(null, null)
|
||||
val positionManager = LocationComponentPositionManager(style, "above", null, false)
|
||||
val requiresUpdate = positionManager.update(null, null, false)
|
||||
assertTrue(requiresUpdate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun update_toNull_below() {
|
||||
val positionManager = LocationComponentPositionManager(style, null, "below")
|
||||
val requiresUpdate = positionManager.update(null, null)
|
||||
val positionManager = LocationComponentPositionManager(style, null, "below", false)
|
||||
val requiresUpdate = positionManager.update(null, null, false)
|
||||
assertTrue(requiresUpdate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun update_fromValue_above() {
|
||||
val positionManager = LocationComponentPositionManager(style, "above1", null)
|
||||
val requiresUpdate = positionManager.update("above2", null)
|
||||
val positionManager = LocationComponentPositionManager(style, "above1", null, false)
|
||||
val requiresUpdate = positionManager.update("above2", null, false)
|
||||
assertTrue(requiresUpdate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun update_fromValue_below() {
|
||||
val positionManager = LocationComponentPositionManager(style, null, "below1")
|
||||
val requiresUpdate = positionManager.update(null, "below2")
|
||||
val positionManager = LocationComponentPositionManager(style, null, "below1", false)
|
||||
val requiresUpdate = positionManager.update(null, "below2", false)
|
||||
assertTrue(requiresUpdate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun update_bearingOnTop_changed() {
|
||||
val positionManager = LocationComponentPositionManager(style, null, null, false)
|
||||
val requiresUpdate = positionManager.update(null, null, true)
|
||||
assertTrue(requiresUpdate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun update_bearingOnTop_changedToFalse() {
|
||||
val positionManager = LocationComponentPositionManager(style, null, null, true)
|
||||
val requiresUpdate = positionManager.update(null, null, false)
|
||||
assertTrue(requiresUpdate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addLayer_noModifier() {
|
||||
val positionManager = LocationComponentPositionManager(style, null, null)
|
||||
val positionManager = LocationComponentPositionManager(style, null, null, false)
|
||||
positionManager.addLayerToMap(layer)
|
||||
|
||||
verify { style.addLayer(layer) }
|
||||
@@ -97,7 +118,7 @@ class LocationComponentPositionManagerTest : BaseTest() {
|
||||
|
||||
@Test
|
||||
fun addLayer_above() {
|
||||
val positionManager = LocationComponentPositionManager(style, "above", null)
|
||||
val positionManager = LocationComponentPositionManager(style, "above", null, false)
|
||||
positionManager.addLayerToMap(layer)
|
||||
|
||||
verify { style.addLayerAbove(layer, "above") }
|
||||
@@ -105,7 +126,7 @@ class LocationComponentPositionManagerTest : BaseTest() {
|
||||
|
||||
@Test
|
||||
fun addLayer_below() {
|
||||
val positionManager = LocationComponentPositionManager(style, null, "below")
|
||||
val positionManager = LocationComponentPositionManager(style, null, "below", false)
|
||||
positionManager.addLayerToMap(layer)
|
||||
|
||||
verify { style.addLayerBelow(layer, "below") }
|
||||
@@ -113,8 +134,8 @@ class LocationComponentPositionManagerTest : BaseTest() {
|
||||
|
||||
@Test
|
||||
fun addLayer_afterUpdate_above() {
|
||||
val positionManager = LocationComponentPositionManager(style, null, null)
|
||||
positionManager.update("above", null)
|
||||
val positionManager = LocationComponentPositionManager(style, null, null, false)
|
||||
positionManager.update("above", null, false)
|
||||
positionManager.addLayerToMap(layer)
|
||||
|
||||
verify { style.addLayerAbove(layer, "above") }
|
||||
@@ -122,8 +143,8 @@ class LocationComponentPositionManagerTest : BaseTest() {
|
||||
|
||||
@Test
|
||||
fun addLayer_afterUpdate_below() {
|
||||
val positionManager = LocationComponentPositionManager(style, null, null)
|
||||
positionManager.update(null, "below")
|
||||
val positionManager = LocationComponentPositionManager(style, null, null, false)
|
||||
positionManager.update(null, "below", false)
|
||||
positionManager.addLayerToMap(layer)
|
||||
|
||||
verify { style.addLayerBelow(layer, "below") }
|
||||
|
||||
@@ -102,7 +102,7 @@ class LocationLayerControllerTest : BaseTest() {
|
||||
false
|
||||
)
|
||||
Mockito.verify(style)
|
||||
.addLayerBelow(shadowLayer, LocationComponentConstants.BACKGROUND_LAYER)
|
||||
.addLayerBelow(shadowLayer, LocationComponentConstants.BEARING_LAYER)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -181,8 +181,7 @@ class LocationLayerControllerTest : BaseTest() {
|
||||
internalRenderModeChangedListener,
|
||||
false
|
||||
)
|
||||
Mockito.verify(style)
|
||||
.addLayerBelow(foregroundLayer, LocationComponentConstants.BEARING_LAYER)
|
||||
Mockito.verify(style).addLayer(foregroundLayer)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -223,7 +222,8 @@ class LocationLayerControllerTest : BaseTest() {
|
||||
internalRenderModeChangedListener,
|
||||
false
|
||||
)
|
||||
Mockito.verify(style).addLayerBelow(bearingLayer, layerBelow)
|
||||
Mockito.verify(style)
|
||||
.addLayerBelow(bearingLayer, LocationComponentConstants.BACKGROUND_LAYER)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -757,13 +757,13 @@ class LocationLayerControllerTest : BaseTest() {
|
||||
Mockito.verify(style).removeLayer(LocationComponentConstants.BACKGROUND_LAYER)
|
||||
Mockito.verify(style).removeLayer(LocationComponentConstants.SHADOW_LAYER)
|
||||
Mockito.verify(style).removeLayer(LocationComponentConstants.ACCURACY_LAYER)
|
||||
Mockito.verify(style).addLayerBelow(bearingLayer2, layerBelow)
|
||||
Mockito.verify(style)
|
||||
.addLayerBelow(foregroundLayer2, LocationComponentConstants.BEARING_LAYER)
|
||||
Mockito.verify(style).addLayerBelow(foregroundLayer2, layerBelow)
|
||||
Mockito.verify(style)
|
||||
.addLayerBelow(backgroundLayer2, LocationComponentConstants.FOREGROUND_LAYER)
|
||||
Mockito.verify(style)
|
||||
.addLayerBelow(shadowLayer2, LocationComponentConstants.BACKGROUND_LAYER)
|
||||
.addLayerBelow(bearingLayer2, LocationComponentConstants.BACKGROUND_LAYER)
|
||||
Mockito.verify(style)
|
||||
.addLayerBelow(shadowLayer2, LocationComponentConstants.BEARING_LAYER)
|
||||
Mockito.verify(style)
|
||||
.addLayerBelow(accuracyLayer2, LocationComponentConstants.BACKGROUND_LAYER)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user