From f387da93093e60a9a844e67d4a44f45dfb4a8b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Paczos?= Date: Wed, 29 Apr 2020 14:58:56 +0200 Subject: [PATCH] Expose LocationComponentOptions#trackingMultiFingerProtectedMoveArea This option provides a way of defining a protected area on the screen which prevents breaking the camera location when executing a multi finger gesture as long as the focal point of the gesture is within this area. --- .../location/LocationCameraController.java | 44 +- .../location/LocationComponentOptions.java | 675 +++++++++--------- .../LocationCameraControllerTest.java | 190 +++++ .../location/LocationModesActivity.java | 13 + .../layout/activity_location_layer_mode.xml | 9 + build.gradle | 2 +- gradle/dependencies.gradle | 2 +- 7 files changed, 584 insertions(+), 351 deletions(-) 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 CREATOR = - new Parcelable.Creator() { + new Parcelable.Creator() { @Override - public LocationComponentOptions createFromParcel(Parcel in) { - return new LocationComponentOptions( - in.readFloat(), - in.readInt(), - in.readInt(), - in.readInt() == 0 ? in.readString() : null, - in.readInt(), - in.readInt() == 0 ? in.readString() : null, - in.readInt(), - in.readInt() == 0 ? in.readString() : null, - in.readInt(), - in.readInt() == 0 ? in.readString() : null, - in.readInt(), - in.readInt() == 0 ? in.readString() : null, - in.readInt(), - in.readInt() == 0 ? in.readString() : null, - in.readInt() == 0 ? in.readInt() : null, - in.readInt() == 0 ? in.readInt() : null, - in.readInt() == 0 ? in.readInt() : null, - in.readInt() == 0 ? in.readInt() : null, - in.readInt() == 0 ? in.readInt() : null, - in.readFloat(), - in.readInt() == 1, - in.readLong(), - in.createIntArray(), - in.readFloat(), - in.readFloat(), - in.readInt() == 1, - in.readFloat(), - in.readFloat(), - in.readString(), - in.readString(), - in.readFloat(), - in.readInt() == 1, - in.readInt() == 1, - in.readInt() == 1, - in.readInt() == 1, - in.readInt() == 0 ? in.readInt() : null, - in.readFloat(), - in.readFloat(), - in.readFloat(), - null - ); + public LocationComponentOptions createFromParcel(Parcel source) { + return new LocationComponentOptions(source); } @Override @@ -1157,114 +1236,6 @@ public class LocationComponentOptions implements Parcelable { } }; - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeFloat(accuracyAlpha()); - dest.writeInt(accuracyColor()); - dest.writeInt(backgroundDrawableStale()); - if (backgroundStaleName() == null) { - dest.writeInt(1); - } else { - dest.writeInt(0); - dest.writeString(backgroundStaleName()); - } - dest.writeInt(foregroundDrawableStale()); - if (foregroundStaleName() == null) { - dest.writeInt(1); - } else { - dest.writeInt(0); - dest.writeString(foregroundStaleName()); - } - dest.writeInt(gpsDrawable()); - if (gpsName() == null) { - dest.writeInt(1); - } else { - dest.writeInt(0); - dest.writeString(gpsName()); - } - dest.writeInt(foregroundDrawable()); - if (foregroundName() == null) { - dest.writeInt(1); - } else { - dest.writeInt(0); - dest.writeString(foregroundName()); - } - dest.writeInt(backgroundDrawable()); - if (backgroundName() == null) { - dest.writeInt(1); - } else { - dest.writeInt(0); - dest.writeString(backgroundName()); - } - dest.writeInt(bearingDrawable()); - if (bearingName() == null) { - dest.writeInt(1); - } else { - dest.writeInt(0); - dest.writeString(bearingName()); - } - if (bearingTintColor() == null) { - dest.writeInt(1); - } else { - dest.writeInt(0); - dest.writeInt(bearingTintColor()); - } - if (foregroundTintColor() == null) { - dest.writeInt(1); - } else { - dest.writeInt(0); - dest.writeInt(foregroundTintColor()); - } - if (backgroundTintColor() == null) { - dest.writeInt(1); - } else { - dest.writeInt(0); - dest.writeInt(backgroundTintColor()); - } - if (foregroundStaleTintColor() == null) { - dest.writeInt(1); - } else { - dest.writeInt(0); - dest.writeInt(foregroundStaleTintColor()); - } - if (backgroundStaleTintColor() == null) { - dest.writeInt(1); - } else { - dest.writeInt(0); - dest.writeInt(backgroundStaleTintColor()); - } - dest.writeFloat(elevation()); - dest.writeInt(enableStaleState() ? 1 : 0); - dest.writeLong(staleStateTimeout()); - dest.writeIntArray(padding()); - dest.writeFloat(maxZoomIconScale()); - dest.writeFloat(minZoomIconScale()); - dest.writeInt(trackingGesturesManagement() ? 1 : 0); - dest.writeFloat(trackingInitialMoveThreshold()); - dest.writeFloat(trackingMultiFingerMoveThreshold()); - dest.writeString(layerAbove()); - dest.writeString(layerBelow()); - dest.writeFloat(trackingAnimationDurationMultiplier); - dest.writeInt(compassAnimationEnabled() ? 1 : 0); - dest.writeInt(accuracyAnimationEnabled() ? 1 : 0); - dest.writeInt(pulseEnabled() ? 1 : 0); - dest.writeInt(pulseFadeEnabled() ? 1 : 0); - if (pulseColor() == null) { - dest.writeInt(1); - } else { - dest.writeInt(0); - dest.writeInt(pulseColor()); - } - dest.writeFloat(pulseSingleDuration()); - dest.writeFloat(pulseMaxRadius()); - dest.writeFloat(pulseAlpha()); - } - - @Override - public int describeContents() { - return 0; - } - /** * Builder class for constructing a new instance of {@link LocationComponentOptions}. */ @@ -1280,17 +1251,17 @@ public class LocationComponentOptions implements Parcelable { LocationComponentOptions locationComponentOptions = autoBuild(); if (locationComponentOptions.accuracyAlpha() < 0 || locationComponentOptions.accuracyAlpha() > 1) { throw new IllegalArgumentException( - "Accuracy alpha value must be between 0.0 and 1.0."); + "Accuracy alpha value must be between 0.0 and 1.0."); } if (locationComponentOptions.elevation() < 0f) { throw new IllegalArgumentException("Invalid shadow size " - + locationComponentOptions.elevation() + ". Must be >= 0"); + + locationComponentOptions.elevation() + ". Must be >= 0"); } if (locationComponentOptions.layerAbove() != null && locationComponentOptions.layerBelow() != null) { throw new IllegalArgumentException("You cannot set both layerAbove and layerBelow options." - + " Choose one or the other."); + + " Choose one or the other."); } if (locationComponentOptions.pulseEnabled() == null) { @@ -1315,8 +1286,8 @@ public class LocationComponentOptions implements Parcelable { } if (!pulsingSetupError.isEmpty()) { throw new IllegalStateException("You've set up the following pulsing circle options but have not enabled" - + " the pulsing circle via the LocationComponentOptions builder:" + pulsingSetupError - + ". Enable the pulsing circle if you're going to set pulsing options."); + + " the pulsing circle via the LocationComponentOptions builder:" + pulsingSetupError + + ". Enable the pulsing circle if you're going to set pulsing options."); } } return locationComponentOptions; @@ -1362,6 +1333,7 @@ public class LocationComponentOptions implements Parcelable { private Boolean trackingGesturesManagement; private Float trackingInitialMoveThreshold; private Float trackingMultiFingerMoveThreshold; + private RectF trackingMultiFingerProtectedMoveArea; private String layerAbove; private String layerBelow; private Float trackingAnimationDurationMultiplier; @@ -1408,6 +1380,7 @@ public class LocationComponentOptions implements Parcelable { this.trackingGesturesManagement = source.trackingGesturesManagement(); this.trackingInitialMoveThreshold = source.trackingInitialMoveThreshold(); this.trackingMultiFingerMoveThreshold = source.trackingMultiFingerMoveThreshold(); + this.trackingMultiFingerProtectedMoveArea = source.trackingMultiFingerProtectedMoveArea(); this.layerAbove = source.layerAbove(); this.layerBelow = source.layerBelow(); this.trackingAnimationDurationMultiplier = source.trackingAnimationDurationMultiplier(); @@ -1814,6 +1787,7 @@ public class LocationComponentOptions implements Parcelable { * false otherwise * @see Builder#trackingInitialMoveThreshold(float) * @see Builder#trackingMultiFingerMoveThreshold(float) + * @see Builder#trackingMultiFingerProtectedMoveArea(RectF) */ @NonNull public LocationComponentOptions.Builder trackingGesturesManagement(boolean trackingGesturesManagement) { @@ -1844,6 +1818,22 @@ public class LocationComponentOptions implements Parcelable { return this; } + /** + * Sets 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. + * + * @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 moveThresholdCaptor = ArgumentCaptor.forClass(Float.class); + verify(moveGestureDetector, atLeastOnce()).setMoveThreshold(moveThresholdCaptor.capture()); + org.junit.Assert.assertEquals(Float.valueOf(0), moveThresholdCaptor.getValue()); + } + + @Test + public void gesturesManagement_moveGesture_multiPointer_tracking() { + MoveGestureDetector moveGestureDetector = mock(MoveGestureDetector.class); + when(moveGestureDetector.getPointersCount()).thenReturn(2); + 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.setCameraMode(TRACKING); + camera.onMoveListener.onMoveBegin(moveGestureDetector); + + verify(moveGestureDetector, atMost(1)).setMoveThreshold(multiFinger); + verify(moveGestureDetector, atMost(1)).setMoveThresholdRect(multiFingerArea); + } + + @Test + public void gesturesManagement_moveGesture_multiPointer_tracking_duplicateCall() { + MoveGestureDetector moveGestureDetector = mock(MoveGestureDetector.class); + when(moveGestureDetector.getPointersCount()).thenReturn(2); + 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.setCameraMode(TRACKING); + camera.onMoveListener.onMoveBegin(moveGestureDetector); + when(moveGestureDetector.getMoveThreshold()).thenReturn(multiFinger); + when(moveGestureDetector.getMoveThresholdRect()).thenReturn(multiFingerArea); + camera.onMoveListener.onMoveBegin(moveGestureDetector); + + verify(moveGestureDetector, atMost(1)).setMoveThreshold(multiFinger); + verify(moveGestureDetector, atMost(1)).setMoveThresholdRect(multiFingerArea); + } + + @Test + public void gesturesManagement_moveGesture_multiPointer_tracking_thresholdMet() { + MoveGestureDetector moveGestureDetector = mock(MoveGestureDetector.class); + when(moveGestureDetector.getPointersCount()).thenReturn(2); + 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); + + // verify the number of detector interruptions + camera.setCameraMode(TRACKING); + camera.onMoveListener.onMoveBegin(moveGestureDetector); + when(moveGestureDetector.getMoveThreshold()).thenReturn(initial); + when(moveGestureDetector.getMoveThreshold()).thenReturn(multiFinger); + when(moveGestureDetector.getMoveThresholdRect()).thenReturn(multiFingerArea); + 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 moveThresholdCaptor = ArgumentCaptor.forClass(Float.class); + verify(moveGestureDetector, atLeastOnce()).setMoveThreshold(moveThresholdCaptor.capture()); + org.junit.Assert.assertEquals(Float.valueOf(0), moveThresholdCaptor.getValue()); + + ArgumentCaptor areaCaptor = ArgumentCaptor.forClass(RectF.class); + verify(moveGestureDetector, atLeastOnce()).setMoveThresholdRect(areaCaptor.capture()); + org.junit.Assert.assertNull(areaCaptor.getValue()); + } + @Test public void onMove_notCancellingTransitionWhileNone() { MapboxMap mapboxMap = mock(MapboxMap.class); diff --git a/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/location/LocationModesActivity.java b/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/location/LocationModesActivity.java index f805918cc..d71570a1b 100644 --- a/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/location/LocationModesActivity.java +++ b/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/location/LocationModesActivity.java @@ -2,6 +2,7 @@ package com.mapbox.mapboxsdk.testapp.activity.location; import android.annotation.SuppressLint; import android.content.res.Configuration; +import android.graphics.RectF; import android.location.Location; import android.os.Bundle; import androidx.annotation.NonNull; @@ -9,6 +10,7 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.ListPopupWindow; import android.view.Menu; import android.view.MenuItem; +import android.view.View; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.Toast; @@ -39,6 +41,7 @@ public class LocationModesActivity extends AppCompatActivity implements OnMapRea private MapView mapView; private Button locationModeBtn; private Button locationTrackingBtn; + private View protectedGestureArea; private PermissionsManager permissionsManager; @@ -64,6 +67,7 @@ public class LocationModesActivity extends AppCompatActivity implements OnMapRea setContentView(R.layout.activity_location_layer_mode); mapView = findViewById(R.id.mapView); + protectedGestureArea = findViewById(R.id.view_protected_gesture_area); locationModeBtn = findViewById(R.id.button_location_mode); locationModeBtn.setOnClickListener(v -> { @@ -245,6 +249,9 @@ public class LocationModesActivity extends AppCompatActivity implements OnMapRea return; } + protectedGestureArea.getLayoutParams().height = 0; + protectedGestureArea.getLayoutParams().width = 0; + LocationComponentOptions options = locationComponent .getLocationComponentOptions() .toBuilder() @@ -258,10 +265,16 @@ public class LocationModesActivity extends AppCompatActivity implements OnMapRea return; } + RectF rectF = new RectF(0f, 0f, mapView.getWidth() / 2f, mapView.getHeight() / 2f); + protectedGestureArea.getLayoutParams().height = (int) rectF.bottom; + protectedGestureArea.getLayoutParams().width = (int) rectF.right; + LocationComponentOptions options = locationComponent .getLocationComponentOptions() .toBuilder() .trackingGesturesManagement(true) + .trackingMultiFingerProtectedMoveArea(rectF) + .trackingMultiFingerMoveThreshold(500) .build(); locationComponent.applyStyle(options); } diff --git a/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_location_layer_mode.xml b/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_location_layer_mode.xml index a13940f29..1a0102c76 100644 --- a/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_location_layer_mode.xml +++ b/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_location_layer_mode.xml @@ -16,6 +16,15 @@ app:layout_constraintTop_toTopOf="parent" app:mapbox_uiAttribution="false" /> + +