diff --git a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationCameraController.java b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationCameraController.java index 13b61dfec..e00cc3884 100644 --- a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationCameraController.java +++ b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationCameraController.java @@ -2,11 +2,13 @@ package com.mapbox.mapboxsdk.location; import android.content.Context; import android.graphics.PointF; +import android.graphics.RectF; import android.location.Location; +import android.view.MotionEvent; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import android.view.MotionEvent; import com.mapbox.android.gestures.AndroidGesturesManager; import com.mapbox.android.gestures.MoveGestureDetector; @@ -314,6 +316,7 @@ final class LocationCameraController { moveGestureDetector.setMoveThreshold(options.trackingInitialMoveThreshold()); } else { moveGestureDetector.setMoveThreshold(0f); + moveGestureDetector.setMoveThresholdRect(null); } } } @@ -359,17 +362,43 @@ final class LocationCameraController { @Override public void onMoveBegin(@NonNull MoveGestureDetector detector) { - if (options.trackingGesturesManagement() - && detector.getPointersCount() > 1 - && detector.getMoveThreshold() != options.trackingMultiFingerMoveThreshold() - && isLocationTracking()) { - detector.setMoveThreshold(options.trackingMultiFingerMoveThreshold()); - interrupt = true; + if (options.trackingGesturesManagement() && isLocationTracking()) { + if (detector.getPointersCount() > 1) { + applyMultiFingerThresholdArea(detector); + applyMultiFingerMoveThreshold(detector); + } else { + applySingleFingerMoveThreshold(detector); + } } else { setCameraMode(CameraMode.NONE); } } + private void applyMultiFingerThresholdArea(@NonNull MoveGestureDetector detector) { + RectF currentRect = detector.getMoveThresholdRect(); + if (currentRect != null && !currentRect.equals(options.trackingMultiFingerProtectedMoveArea())) { + detector.setMoveThresholdRect(options.trackingMultiFingerProtectedMoveArea()); + interrupt = true; + } else if (currentRect == null && options.trackingMultiFingerProtectedMoveArea() != null) { + detector.setMoveThresholdRect(options.trackingMultiFingerProtectedMoveArea()); + interrupt = true; + } + } + + private void applyMultiFingerMoveThreshold(@NonNull MoveGestureDetector detector) { + if (detector.getMoveThreshold() != options.trackingMultiFingerMoveThreshold()) { + detector.setMoveThreshold(options.trackingMultiFingerMoveThreshold()); + interrupt = true; + } + } + + private void applySingleFingerMoveThreshold(@NonNull MoveGestureDetector detector) { + if (detector.getMoveThreshold() != options.trackingInitialMoveThreshold()) { + detector.setMoveThreshold(options.trackingInitialMoveThreshold()); + interrupt = true; + } + } + @Override public void onMove(@NonNull MoveGestureDetector detector) { if (interrupt) { @@ -387,6 +416,7 @@ final class LocationCameraController { public void onMoveEnd(@NonNull MoveGestureDetector detector) { if (options.trackingGesturesManagement() && !interrupt && isLocationTracking()) { detector.setMoveThreshold(options.trackingInitialMoveThreshold()); + detector.setMoveThresholdRect(null); } interrupt = false; } diff --git a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java index 781d67eb3..7e0d65963 100644 --- a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java +++ b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java @@ -3,16 +3,11 @@ package com.mapbox.mapboxsdk.location; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; +import android.graphics.RectF; import android.os.Parcel; import android.os.Parcelable; import android.view.animation.Interpolator; -import com.mapbox.android.gestures.AndroidGesturesManager; -import com.mapbox.mapboxsdk.R; -import com.mapbox.mapboxsdk.style.layers.Layer; - -import java.util.Arrays; - import androidx.annotation.ColorInt; import androidx.annotation.Dimension; import androidx.annotation.DrawableRes; @@ -20,6 +15,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StyleRes; +import com.mapbox.android.gestures.AndroidGesturesManager; +import com.mapbox.mapboxsdk.R; +import com.mapbox.mapboxsdk.style.layers.Layer; + +import java.util.Arrays; + /** * 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} @@ -126,6 +127,8 @@ public class LocationComponentOptions implements Parcelable { private boolean trackingGesturesManagement; private float trackingInitialMoveThreshold; private float trackingMultiFingerMoveThreshold; + @Nullable + private RectF trackingMultiFingerProtectedMoveArea; private String layerAbove; private String layerBelow; private float trackingAnimationDurationMultiplier; @@ -141,46 +144,47 @@ public class LocationComponentOptions implements Parcelable { 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, - Boolean pulseEnabled, - Boolean pulseFadeEnabled, - Integer pulseColor, - float pulseSingleDuration, - float pulseMaxRadius, - float pulseAlpha, - @Nullable Interpolator pulseInterpolator) { + 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, + RectF trackingMultiFingerProtectedMoveArea, + 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; @@ -212,6 +216,7 @@ public class LocationComponentOptions implements Parcelable { this.trackingGesturesManagement = trackingGesturesManagement; this.trackingInitialMoveThreshold = trackingInitialMoveThreshold; this.trackingMultiFingerMoveThreshold = trackingMultiFingerMoveThreshold; + this.trackingMultiFingerProtectedMoveArea = trackingMultiFingerProtectedMoveArea; this.layerAbove = layerAbove; this.layerBelow = layerBelow; this.trackingAnimationDurationMultiplier = trackingAnimationDurationMultiplier; @@ -241,130 +246,130 @@ 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 + R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleEnabled, false ); builder.pulseFadeEnabled = typedArray.getBoolean( - R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleFadeEnabled, true + 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)); + R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleColor, + -1)); } builder.pulseSingleDuration = typedArray.getFloat( - R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleDuration, CIRCLE_PULSING_DURATION_DEFAULT_MS + 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 + 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); + R.styleable.mapbox_LocationComponent_mapbox_pulsingLocationCircleAlpha, CIRCLE_PULSING_ALPHA_DEFAULT); typedArray.recycle(); @@ -394,7 +399,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(); } /** @@ -729,6 +734,7 @@ public class LocationComponentOptions implements Parcelable { * @return true if gestures are adjusted when in one of the camera tracking modes, false otherwise * @see Builder#trackingInitialMoveThreshold(float) * @see Builder#trackingMultiFingerMoveThreshold(float) + * @see Builder#trackingMultiFingerProtectedMoveArea(RectF) */ public boolean trackingGesturesManagement() { return trackingGesturesManagement; @@ -752,6 +758,20 @@ public class LocationComponentOptions implements Parcelable { return trackingMultiFingerMoveThreshold; } + /** + * Protected multi pointer gesture area. When the camera is in a tracking mode, any multi finger gesture with focal + * point inside the provided screen coordinate rectangle is not going to break the tracking. + *
+ * Best paired with the {@link LocationComponentOptions.Builder#trackingMultiFingerMoveThreshold(float)}
+ * set to 0 or a relatively small value to not interfere with gestures outside of the defined rectangle.
+ *
+ * @return the protected multi finger area while camera is tracking
+ */
+ @Nullable
+ public RectF trackingMultiFingerProtectedMoveArea() {
+ return trackingMultiFingerProtectedMoveArea;
+ }
+
/**
* Gets the id of the layer that's referenced when placing the component on the map using
* {@link com.mapbox.mapboxsdk.maps.Style#addLayerAbove(Layer, String)}.
@@ -874,44 +894,45 @@ public class LocationComponentOptions implements Parcelable {
@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
- + "pulseEnabled=" + pulseEnabled
- + "pulseFadeEnabled=" + pulseFadeEnabled
- + "pulseColor=" + pulseColor
- + "pulseSingleDuration=" + pulseSingleDuration
- + "pulseMaxRadius=" + pulseMaxRadius
- + "pulseAlpha=" + pulseAlpha
- + "}";
+ + "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 + ", "
+ + "trackingMultiFingerProtectedMoveArea=" + trackingMultiFingerProtectedMoveArea + ", "
+ + "layerAbove=" + layerAbove
+ + "layerBelow=" + layerBelow
+ + "trackingAnimationDurationMultiplier=" + trackingAnimationDurationMultiplier
+ + "pulseEnabled=" + pulseEnabled
+ + "pulseFadeEnabled=" + pulseFadeEnabled
+ + "pulseColor=" + pulseColor
+ + "pulseSingleDuration=" + pulseSingleDuration
+ + "pulseMaxRadius=" + pulseMaxRadius
+ + "pulseAlpha=" + pulseAlpha
+ + "}";
}
@Override
@@ -976,6 +997,11 @@ public class LocationComponentOptions implements Parcelable {
if (Float.compare(options.trackingAnimationDurationMultiplier, trackingAnimationDurationMultiplier) != 0) {
return false;
}
+ if (trackingMultiFingerProtectedMoveArea != null
+ ? !trackingMultiFingerProtectedMoveArea.equals(options.trackingMultiFingerProtectedMoveArea) :
+ options.trackingMultiFingerProtectedMoveArea != null) {
+ return false;
+ }
if (compassAnimationEnabled != options.compassAnimationEnabled) {
return false;
}
@@ -983,11 +1009,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) {
@@ -1003,23 +1029,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)) {
@@ -1038,7 +1064,7 @@ public class LocationComponentOptions implements Parcelable {
}
if (pulseColor != null ? !pulseColor.equals(options.pulseColor) :
- options.pulseColor() != null) {
+ options.pulseColor() != null) {
return false;
}
if (Float.compare(options.pulseSingleDuration, pulseSingleDuration) != 0) {
@@ -1085,13 +1111,15 @@ 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 + (trackingMultiFingerProtectedMoveArea != null
+ ? trackingMultiFingerProtectedMoveArea.hashCode() : 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);
@@ -1103,52 +1131,103 @@ public class LocationComponentOptions implements Parcelable {
return result;
}
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(this.accuracyAlpha);
+ dest.writeInt(this.accuracyColor);
+ dest.writeInt(this.backgroundDrawableStale);
+ dest.writeString(this.backgroundStaleName);
+ dest.writeInt(this.foregroundDrawableStale);
+ dest.writeString(this.foregroundStaleName);
+ dest.writeInt(this.gpsDrawable);
+ dest.writeString(this.gpsName);
+ dest.writeInt(this.foregroundDrawable);
+ dest.writeString(this.foregroundName);
+ dest.writeInt(this.backgroundDrawable);
+ dest.writeString(this.backgroundName);
+ dest.writeInt(this.bearingDrawable);
+ dest.writeString(this.bearingName);
+ dest.writeValue(this.bearingTintColor);
+ dest.writeValue(this.foregroundTintColor);
+ dest.writeValue(this.backgroundTintColor);
+ dest.writeValue(this.foregroundStaleTintColor);
+ dest.writeValue(this.backgroundStaleTintColor);
+ dest.writeFloat(this.elevation);
+ dest.writeByte(this.enableStaleState ? (byte) 1 : (byte) 0);
+ dest.writeLong(this.staleStateTimeout);
+ dest.writeIntArray(this.padding);
+ dest.writeFloat(this.maxZoomIconScale);
+ dest.writeFloat(this.minZoomIconScale);
+ dest.writeByte(this.trackingGesturesManagement ? (byte) 1 : (byte) 0);
+ dest.writeFloat(this.trackingInitialMoveThreshold);
+ dest.writeFloat(this.trackingMultiFingerMoveThreshold);
+ dest.writeParcelable(this.trackingMultiFingerProtectedMoveArea, flags);
+ dest.writeString(this.layerAbove);
+ dest.writeString(this.layerBelow);
+ dest.writeFloat(this.trackingAnimationDurationMultiplier);
+ dest.writeByte(this.compassAnimationEnabled ? (byte) 1 : (byte) 0);
+ dest.writeByte(this.accuracyAnimationEnabled ? (byte) 1 : (byte) 0);
+ dest.writeValue(this.pulseEnabled);
+ dest.writeValue(this.pulseFadeEnabled);
+ dest.writeValue(this.pulseColor);
+ dest.writeFloat(this.pulseSingleDuration);
+ dest.writeFloat(this.pulseMaxRadius);
+ dest.writeFloat(this.pulseAlpha);
+ }
+
+ protected LocationComponentOptions(Parcel in) {
+ this.accuracyAlpha = in.readFloat();
+ this.accuracyColor = in.readInt();
+ this.backgroundDrawableStale = in.readInt();
+ this.backgroundStaleName = in.readString();
+ this.foregroundDrawableStale = in.readInt();
+ this.foregroundStaleName = in.readString();
+ this.gpsDrawable = in.readInt();
+ this.gpsName = in.readString();
+ this.foregroundDrawable = in.readInt();
+ this.foregroundName = in.readString();
+ this.backgroundDrawable = in.readInt();
+ this.backgroundName = in.readString();
+ this.bearingDrawable = in.readInt();
+ this.bearingName = in.readString();
+ this.bearingTintColor = (Integer) in.readValue(Integer.class.getClassLoader());
+ this.foregroundTintColor = (Integer) in.readValue(Integer.class.getClassLoader());
+ this.backgroundTintColor = (Integer) in.readValue(Integer.class.getClassLoader());
+ this.foregroundStaleTintColor = (Integer) in.readValue(Integer.class.getClassLoader());
+ this.backgroundStaleTintColor = (Integer) in.readValue(Integer.class.getClassLoader());
+ this.elevation = in.readFloat();
+ this.enableStaleState = in.readByte() != 0;
+ this.staleStateTimeout = in.readLong();
+ this.padding = in.createIntArray();
+ this.maxZoomIconScale = in.readFloat();
+ this.minZoomIconScale = in.readFloat();
+ this.trackingGesturesManagement = in.readByte() != 0;
+ this.trackingInitialMoveThreshold = in.readFloat();
+ this.trackingMultiFingerMoveThreshold = in.readFloat();
+ this.trackingMultiFingerProtectedMoveArea = in.readParcelable(RectF.class.getClassLoader());
+ this.layerAbove = in.readString();
+ this.layerBelow = in.readString();
+ this.trackingAnimationDurationMultiplier = in.readFloat();
+ this.compassAnimationEnabled = in.readByte() != 0;
+ this.accuracyAnimationEnabled = in.readByte() != 0;
+ this.pulseEnabled = (Boolean) in.readValue(Boolean.class.getClassLoader());
+ this.pulseFadeEnabled = (Boolean) in.readValue(Boolean.class.getClassLoader());
+ this.pulseColor = (Integer) in.readValue(Integer.class.getClassLoader());
+ this.pulseSingleDuration = in.readFloat();
+ this.pulseMaxRadius = in.readFloat();
+ this.pulseAlpha = in.readFloat();
+ }
+
public static final Parcelable.Creator
+ * Best paired with the {@link LocationComponentOptions.Builder#trackingMultiFingerMoveThreshold(float)}
+ * set to 0 or a relatively small value to not interfere with gestures outside of the defined rectangle.
+ *
+ * @param rect the protected multi finger area while camera is tracking
+ */
+ @NonNull
+ public LocationComponentOptions.Builder trackingMultiFingerProtectedMoveArea(@Nullable RectF rect) {
+ this.trackingMultiFingerProtectedMoveArea = rect;
+ return this;
+ }
+
/**
* Sets the id of the layer that's referenced when placing the component on the map using
* {@link com.mapbox.mapboxsdk.maps.Style#addLayerAbove(Layer, String)}.
@@ -1879,7 +1869,7 @@ public class LocationComponentOptions implements Parcelable {
*/
@NonNull
public LocationComponentOptions.Builder trackingAnimationDurationMultiplier(
- float trackingAnimationDurationMultiplier) {
+ float trackingAnimationDurationMultiplier) {
this.trackingAnimationDurationMultiplier = trackingAnimationDurationMultiplier;
return this;
}
@@ -1971,7 +1961,7 @@ public class LocationComponentOptions implements Parcelable {
* Sets the pulsing circle's interpolator animation.
*
* @param pulseInterpolator the type of Android-system interpolator to use when
- * creating the pulsing animation
+ * creating the pulsing animation
* @return a String which represents the interpolator animation that the pulsing circle will use.
*/
public LocationComponentOptions.Builder pulseInterpolator(Interpolator pulseInterpolator) {
@@ -2040,46 +2030,47 @@ 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.pulseEnabled,
- this.pulseFadeEnabled,
- this.pulseColor,
- this.pulseSingleDuration,
- this.pulseMaxRadius,
- this.pulseAlpha,
- this.pulseInterpolator);
+ 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.trackingMultiFingerProtectedMoveArea,
+ this.layerAbove,
+ this.layerBelow,
+ this.trackingAnimationDurationMultiplier,
+ this.compassAnimationEnabled,
+ this.accuracyAnimationEnabled,
+ this.pulseEnabled,
+ this.pulseFadeEnabled,
+ this.pulseColor,
+ this.pulseSingleDuration,
+ this.pulseMaxRadius,
+ this.pulseAlpha,
+ this.pulseInterpolator);
}
}
}
diff --git a/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationCameraControllerTest.java b/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationCameraControllerTest.java
index f068c94ce..acf5a9dac 100644
--- a/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationCameraControllerTest.java
+++ b/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationCameraControllerTest.java
@@ -1,6 +1,7 @@
package com.mapbox.mapboxsdk.location;
import android.graphics.PointF;
+import android.graphics.RectF;
import android.location.Location;
import com.mapbox.android.gestures.AndroidGesturesManager;
@@ -40,6 +41,8 @@ import static junit.framework.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -73,6 +76,7 @@ public class LocationCameraControllerTest {
camera.setCameraMode(TRACKING_GPS);
verify(moveGestureDetector).setMoveThreshold(moveThreshold);
+ verify(moveGestureDetector, times(0)).setMoveThresholdRect(any(RectF.class));
}
@Test
@@ -89,6 +93,7 @@ public class LocationCameraControllerTest {
verify(moveGestureDetector, times(0)).setMoveThreshold(moveThreshold);
verify(moveGestureDetector, times(0)).setMoveThreshold(0f);
+ verify(moveGestureDetector, times(0)).setMoveThresholdRect(any(RectF.class));
}
@Test
@@ -102,6 +107,7 @@ public class LocationCameraControllerTest {
camera.setCameraMode(NONE);
verify(moveGestureDetector, times(2)).setMoveThreshold(0f); // one for initialization
+ verify(moveGestureDetector, times(2)).setMoveThresholdRect(null); // one for initialization
}
@Test
@@ -495,6 +501,190 @@ public class LocationCameraControllerTest {
verify(mapboxMap, times(0)).setGesturesManager(internalGesturesManager, true, true);
}
+ @Test
+ public void gesturesManagement_moveGesture_notTracking() {
+ MoveGestureDetector moveGestureDetector = mock(MoveGestureDetector.class);
+ when(moveGestureDetector.getPointersCount()).thenReturn(1);
+ LocationCameraController camera = buildCamera(moveGestureDetector);
+ LocationComponentOptions options = mock(LocationComponentOptions.class);
+ when(options.trackingGesturesManagement()).thenReturn(true);
+ float initial = 100;
+ float multiFinger = 200;
+ RectF multiFingerArea = mock(RectF.class);
+ when(options.trackingInitialMoveThreshold()).thenReturn(initial);
+ when(options.trackingMultiFingerMoveThreshold()).thenReturn(multiFinger);
+ when(options.trackingMultiFingerProtectedMoveArea()).thenReturn(multiFingerArea);
+ camera.initializeOptions(options);
+
+ camera.onMoveListener.onMoveBegin(moveGestureDetector);
+
+ verify(moveGestureDetector, times(2)).setMoveThreshold(0);
+ verify(moveGestureDetector, times(2)).setMoveThresholdRect(null);
+ }
+
+ @Test
+ public void gesturesManagement_moveGesture_singlePointer_tracking() {
+ MoveGestureDetector moveGestureDetector = mock(MoveGestureDetector.class);
+ when(moveGestureDetector.getPointersCount()).thenReturn(1);
+ LocationCameraController camera = buildCamera(moveGestureDetector);
+ LocationComponentOptions options = mock(LocationComponentOptions.class);
+ when(options.trackingGesturesManagement()).thenReturn(true);
+ float initial = 100;
+ when(options.trackingInitialMoveThreshold()).thenReturn(initial);
+ camera.initializeOptions(options);
+
+ camera.setCameraMode(TRACKING);
+ when(moveGestureDetector.getMoveThreshold()).thenReturn(initial);
+ camera.onMoveListener.onMoveBegin(moveGestureDetector);
+
+ verify(moveGestureDetector, atMost(1)).setMoveThreshold(initial);
+ verify(moveGestureDetector, times(0)).setMoveThresholdRect(any(RectF.class));
+ }
+
+ @Test
+ public void gesturesManagement_moveGesture_singlePointer_tracking_duplicateCall() {
+ MoveGestureDetector moveGestureDetector = mock(MoveGestureDetector.class);
+ when(moveGestureDetector.getPointersCount()).thenReturn(1);
+ LocationCameraController camera = buildCamera(moveGestureDetector);
+ LocationComponentOptions options = mock(LocationComponentOptions.class);
+ when(options.trackingGesturesManagement()).thenReturn(true);
+ float initial = 100;
+ when(options.trackingInitialMoveThreshold()).thenReturn(initial);
+ camera.initializeOptions(options);
+
+ camera.setCameraMode(TRACKING);
+ when(moveGestureDetector.getMoveThreshold()).thenReturn(initial);
+ camera.onMoveListener.onMoveBegin(moveGestureDetector);
+
+ verify(moveGestureDetector, atMost(1)).setMoveThreshold(initial);
+ verify(moveGestureDetector, times(0)).setMoveThresholdRect(any(RectF.class));
+ }
+
+ @Test
+ public void gesturesManagement_moveGesture_singlePointer_tracking_thresholdMet() {
+ MoveGestureDetector moveGestureDetector = mock(MoveGestureDetector.class);
+ when(moveGestureDetector.getPointersCount()).thenReturn(1);
+ LocationCameraController camera = buildCamera(moveGestureDetector);
+ LocationComponentOptions options = mock(LocationComponentOptions.class);
+ when(options.trackingGesturesManagement()).thenReturn(true);
+ float initial = 100;
+ when(options.trackingInitialMoveThreshold()).thenReturn(initial);
+ camera.initializeOptions(options);
+
+ // verify the number of detector interruptions
+ camera.setCameraMode(TRACKING);
+ camera.onMoveListener.onMoveBegin(moveGestureDetector);
+ when(moveGestureDetector.getMoveThreshold()).thenReturn(initial);
+ camera.onMoveListener.onMove(moveGestureDetector);
+ verify(moveGestureDetector, times(1)).interrupt();
+ camera.onMoveListener.onMoveEnd(moveGestureDetector);
+ camera.onMoveListener.onMoveBegin(moveGestureDetector);
+ camera.onMoveListener.onMove(moveGestureDetector);
+ verify(moveGestureDetector, times(2)).interrupt();
+ camera.onMoveListener.onMoveEnd(moveGestureDetector);
+ camera.onMoveListener.onMoveBegin(moveGestureDetector);
+ camera.onMoveListener.onMove(moveGestureDetector);
+ camera.onMoveListener.onMoveEnd(moveGestureDetector);
+
+ verify(moveGestureDetector, times(2)).interrupt();
+
+ // verify that threshold are reset
+ ArgumentCaptor