[android] improved gestures management while tracking location

This commit is contained in:
Łukasz Paczos
2018-09-05 19:06:38 +02:00
committed by Łukasz Paczos
parent 907612e93d
commit 8cd86fe1b4
10 changed files with 142 additions and 50 deletions

View File

@@ -2,6 +2,7 @@ package com.mapbox.mapboxsdk.location;
import android.content.Context;
import android.graphics.PointF;
import android.support.annotation.NonNull;
import android.view.MotionEvent;
import com.mapbox.android.gestures.AndroidGesturesManager;
@@ -9,11 +10,8 @@ import com.mapbox.android.gestures.MoveGestureDetector;
import com.mapbox.android.gestures.RotateGestureDetector;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.location.modes.CameraMode;
import java.util.List;
import java.util.Set;
import com.mapbox.mapboxsdk.maps.MapboxMap;
final class LocationCameraController implements MapboxAnimator.OnCameraAnimationsValuesChangeListener {
@@ -28,6 +26,9 @@ final class LocationCameraController implements MapboxAnimator.OnCameraAnimation
private final MoveGestureDetector moveGestureDetector;
private final OnCameraMoveInvalidateListener onCameraMoveInvalidateListener;
private final AndroidGesturesManager initialGesturesManager;
private final AndroidGesturesManager internalGesturesManager;
LocationCameraController(
Context context,
MapboxMap mapboxMap,
@@ -35,12 +36,13 @@ final class LocationCameraController implements MapboxAnimator.OnCameraAnimation
LocationComponentOptions options,
OnCameraMoveInvalidateListener onCameraMoveInvalidateListener) {
this.mapboxMap = mapboxMap;
mapboxMap.setGesturesManager(
new LocationGesturesManager(context), true, true);
moveGestureDetector = mapboxMap.getGesturesManager().getMoveGestureDetector();
mapboxMap.addOnMoveListener(onMoveListener);
initialGesturesManager = mapboxMap.getGesturesManager();
internalGesturesManager = new LocationGesturesManager(context);
moveGestureDetector = internalGesturesManager.getMoveGestureDetector();
mapboxMap.addOnRotateListener(onRotateListener);
mapboxMap.addOnFlingListener(onFlingListener);
mapboxMap.addOnMoveListener(onMoveListener);
this.internalCameraTrackingChangedListener = internalCameraTrackingChangedListener;
this.onCameraMoveInvalidateListener = onCameraMoveInvalidateListener;
@@ -51,15 +53,24 @@ final class LocationCameraController implements MapboxAnimator.OnCameraAnimation
LocationCameraController(MapboxMap mapboxMap,
MoveGestureDetector moveGestureDetector,
OnCameraTrackingChangedListener internalCameraTrackingChangedListener,
OnCameraMoveInvalidateListener onCameraMoveInvalidateListener) {
OnCameraMoveInvalidateListener onCameraMoveInvalidateListener,
AndroidGesturesManager androidGesturesManager) {
this.mapboxMap = mapboxMap;
this.moveGestureDetector = moveGestureDetector;
this.internalCameraTrackingChangedListener = internalCameraTrackingChangedListener;
this.onCameraMoveInvalidateListener = onCameraMoveInvalidateListener;
this.internalGesturesManager = androidGesturesManager;
this.initialGesturesManager = androidGesturesManager;
}
void initializeOptions(LocationComponentOptions options) {
this.options = options;
if (options.trackingGesturesManagement()) {
mapboxMap.setGesturesManager(internalGesturesManager, true, true);
adjustGesturesThresholds();
} else {
mapboxMap.setGesturesManager(initialGesturesManager, true, true);
}
}
void setCameraMode(@CameraMode.Mode int cameraMode) {
@@ -141,11 +152,13 @@ final class LocationCameraController implements MapboxAnimator.OnCameraAnimation
}
private void adjustGesturesThresholds() {
if (isLocationTracking()) {
adjustFocalPoint = true;
moveGestureDetector.setMoveThreshold(options.trackingInitialMoveThreshold());
} else {
moveGestureDetector.setMoveThreshold(0f);
if (options.trackingGesturesManagement()) {
if (isLocationTracking()) {
adjustFocalPoint = true;
moveGestureDetector.setMoveThreshold(options.trackingInitialMoveThreshold());
} else {
moveGestureDetector.setMoveThreshold(0f);
}
}
}
@@ -176,29 +189,31 @@ final class LocationCameraController implements MapboxAnimator.OnCameraAnimation
private boolean interrupt;
@Override
public void onMoveBegin(MoveGestureDetector detector) {
if (detector.getPointersCount() > 1
public void onMoveBegin(@NonNull MoveGestureDetector detector) {
if (options.trackingGesturesManagement()
&& detector.getPointersCount() > 1
&& detector.getMoveThreshold() != options.trackingMultiFingerMoveThreshold()
&& isLocationTracking()) {
detector.setMoveThreshold(options.trackingMultiFingerMoveThreshold());
interrupt = true;
} else {
setCameraMode(CameraMode.NONE);
}
}
@Override
public void onMove(MoveGestureDetector detector) {
public void onMove(@NonNull MoveGestureDetector detector) {
if (interrupt) {
detector.interrupt();
return;
}
setCameraMode(CameraMode.NONE);
}
@Override
public void onMoveEnd(MoveGestureDetector detector) {
if (!interrupt && isLocationTracking()) {
moveGestureDetector.setMoveThreshold(options.trackingInitialMoveThreshold());
public void onMoveEnd(@NonNull MoveGestureDetector detector) {
if (options.trackingGesturesManagement() && !interrupt && isLocationTracking()) {
detector.setMoveThreshold(options.trackingInitialMoveThreshold());
}
interrupt = false;
}
@@ -206,19 +221,19 @@ final class LocationCameraController implements MapboxAnimator.OnCameraAnimation
private MapboxMap.OnRotateListener onRotateListener = new MapboxMap.OnRotateListener() {
@Override
public void onRotateBegin(RotateGestureDetector detector) {
public void onRotateBegin(@NonNull RotateGestureDetector detector) {
if (isBearingTracking()) {
setCameraMode(CameraMode.NONE);
}
}
@Override
public void onRotate(RotateGestureDetector detector) {
public void onRotate(@NonNull RotateGestureDetector detector) {
// no implementation
}
@Override
public void onRotateEnd(RotateGestureDetector detector) {
public void onRotateEnd(@NonNull RotateGestureDetector detector) {
// no implementation
}
};
@@ -232,23 +247,10 @@ final class LocationCameraController implements MapboxAnimator.OnCameraAnimation
private class LocationGesturesManager extends AndroidGesturesManager {
public LocationGesturesManager(Context context) {
LocationGesturesManager(Context context) {
super(context);
}
public LocationGesturesManager(Context context, boolean applyDefaultThresholds) {
super(context, applyDefaultThresholds);
}
public LocationGesturesManager(Context context, Set<Integer>[] exclusiveGestures) {
super(context, exclusiveGestures);
}
public LocationGesturesManager(Context context, List<Set<Integer>> exclusiveGestures,
boolean applyDefaultThresholds) {
super(context, exclusiveGestures, applyDefaultThresholds);
}
@Override
public boolean onTouchEvent(MotionEvent motionEvent) {
if (motionEvent != null) {

View File

@@ -351,6 +351,7 @@ public final class LocationComponent {
public void applyStyle(LocationComponentOptions options) {
this.options = options;
locationLayerController.applyStyle(options);
locationCameraController.initializeOptions(options);
staleStateManager.setEnabled(options.enableStaleState());
staleStateManager.setDelayTime(options.staleStateTimeout());
updateMapWithOptions(options);

View File

@@ -12,6 +12,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StyleRes;
import com.mapbox.android.gestures.AndroidGesturesManager;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
@@ -100,6 +101,7 @@ public class LocationComponentOptions implements Parcelable {
private double minZoom;
private float maxZoomIconScale;
private float minZoomIconScale;
private boolean trackingGesturesManagement;
private float trackingInitialMoveThreshold;
private float trackingMultiFingerMoveThreshold;
private String layerBelow;
@@ -132,6 +134,7 @@ public class LocationComponentOptions implements Parcelable {
double minZoom,
float maxZoomIconScale,
float minZoomIconScale,
boolean trackingGesturesManagement,
float trackingInitialMoveThreshold,
float trackingMultiFingerMoveThreshold,
String layerBelow) {
@@ -165,6 +168,7 @@ public class LocationComponentOptions implements Parcelable {
this.minZoom = minZoom;
this.maxZoomIconScale = maxZoomIconScale;
this.minZoomIconScale = minZoomIconScale;
this.trackingGesturesManagement = trackingGesturesManagement;
this.trackingInitialMoveThreshold = trackingInitialMoveThreshold;
this.trackingMultiFingerMoveThreshold = trackingMultiFingerMoveThreshold;
this.layerBelow = layerBelow;
@@ -243,10 +247,11 @@ public class LocationComponentOptions implements Parcelable {
R.styleable.mapbox_LocationComponent_mapbox_accuracyAlpha, ACCURACY_ALPHA_DEFAULT));
builder.elevation(elevation);
builder.trackingGesturesManagement(typedArray.getBoolean(
R.styleable.mapbox_LocationComponent_mapbox_trackingGesturesManagement, false));
builder.trackingInitialMoveThreshold(typedArray.getDimension(
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)));
@@ -653,6 +658,23 @@ public class LocationComponentOptions implements Parcelable {
return minZoomIconScale;
}
/**
* Returns whether gesture threshold should be adjusted when camera is in one of the tracking modes.
* This will adjust the focal point and increase thresholds to enable camera manipulation,
* like zooming in and out, without breaking tracking.
* <p>
* <strong>Note</strong>: If set to true, this can overwrite some of the gesture thresholds
* and the custom {@link com.mapbox.android.gestures.AndroidGesturesManager} that was set with
* {@link com.mapbox.mapboxsdk.maps.MapboxMap#setGesturesManager(AndroidGesturesManager, boolean, boolean)}.
*
* @return true if gestures are adjusted when in one of the camera tracking modes, false otherwise
* @see Builder#trackingInitialMoveThreshold(float)
* @see Builder#trackingMultiFingerMoveThreshold(float)
*/
public boolean trackingGesturesManagement() {
return trackingGesturesManagement;
}
/**
* Minimum single pointer movement in pixels required to break camera tracking.
*
@@ -710,6 +732,7 @@ public class LocationComponentOptions implements Parcelable {
+ "minZoom=" + minZoom + ", "
+ "maxZoomIconScale=" + maxZoomIconScale + ", "
+ "minZoomIconScale=" + minZoomIconScale + ", "
+ "trackingGesturesManagement=" + trackingGesturesManagement + ", "
+ "trackingInitialMoveThreshold=" + trackingInitialMoveThreshold + ", "
+ "trackingMultiFingerMoveThreshold=" + trackingMultiFingerMoveThreshold + ", "
+ "layerBelow=" + layerBelow
@@ -760,6 +783,7 @@ public class LocationComponentOptions implements Parcelable {
&& (Double.doubleToLongBits(this.minZoom) == Double.doubleToLongBits(that.minZoom()))
&& (Float.floatToIntBits(this.maxZoomIconScale) == Float.floatToIntBits(that.maxZoomIconScale()))
&& (Float.floatToIntBits(this.minZoomIconScale) == Float.floatToIntBits(that.minZoomIconScale()))
&& (this.trackingGesturesManagement == that.trackingGesturesManagement())
&& (Float.floatToIntBits(this.trackingInitialMoveThreshold)
== Float.floatToIntBits(that.trackingInitialMoveThreshold()))
&& (Float.floatToIntBits(this.trackingMultiFingerMoveThreshold)
@@ -827,6 +851,8 @@ public class LocationComponentOptions implements Parcelable {
h$ *= 1000003;
h$ ^= Float.floatToIntBits(minZoomIconScale);
h$ *= 1000003;
h$ ^= trackingGesturesManagement ? 1231 : 1237;
h$ *= 1000003;
h$ ^= Float.floatToIntBits(trackingInitialMoveThreshold);
h$ *= 1000003;
h$ ^= Float.floatToIntBits(trackingMultiFingerMoveThreshold);
@@ -865,6 +891,7 @@ public class LocationComponentOptions implements Parcelable {
in.readDouble(),
in.readFloat(),
in.readFloat(),
in.readInt() == 1,
in.readFloat(),
in.readFloat(),
in.readString()
@@ -961,6 +988,7 @@ public class LocationComponentOptions implements Parcelable {
dest.writeDouble(minZoom());
dest.writeFloat(maxZoomIconScale());
dest.writeFloat(minZoomIconScale());
dest.writeInt(trackingGesturesManagement() ? 1 : 0);
dest.writeFloat(trackingInitialMoveThreshold());
dest.writeFloat(trackingMultiFingerMoveThreshold());
dest.writeString(layerBelow());
@@ -1023,6 +1051,7 @@ public class LocationComponentOptions implements Parcelable {
private Double minZoom;
private Float maxZoomIconScale;
private Float minZoomIconScale;
private Boolean trackingGesturesManagement;
private Float trackingInitialMoveThreshold;
private Float trackingMultiFingerMoveThreshold;
private String layerBelow;
@@ -1058,6 +1087,7 @@ public class LocationComponentOptions implements Parcelable {
this.minZoom = source.minZoom();
this.maxZoomIconScale = source.maxZoomIconScale();
this.minZoomIconScale = source.minZoomIconScale();
this.trackingGesturesManagement = source.trackingGesturesManagement();
this.trackingInitialMoveThreshold = source.trackingInitialMoveThreshold();
this.trackingMultiFingerMoveThreshold = source.trackingMultiFingerMoveThreshold();
this.layerBelow = source.layerBelow();
@@ -1435,6 +1465,25 @@ public class LocationComponentOptions implements Parcelable {
return this;
}
/**
* Set whether gesture threshold should be adjusted when camera is in one of the tracking modes.
* This will adjust the focal point and increase thresholds to enable camera manipulation,
* like zooming in and out, without breaking tracking.
* <p>
* <strong>Note</strong>: This can overwrite some of the gesture thresholds
* and the custom {@link com.mapbox.android.gestures.AndroidGesturesManager} that was set with
* {@link com.mapbox.mapboxsdk.maps.MapboxMap#setGesturesManager(AndroidGesturesManager, boolean, boolean)}.
*
* @param trackingGesturesManagement true if gestures should be adjusted when in one of the camera tracking modes,
* false otherwise
* @see Builder#trackingInitialMoveThreshold(float)
* @see Builder#trackingMultiFingerMoveThreshold(float)
*/
public LocationComponentOptions.Builder trackingGesturesManagement(boolean trackingGesturesManagement) {
this.trackingGesturesManagement = trackingGesturesManagement;
return this;
}
/**
* Sets minimum single pointer movement (map pan) in pixels required to break camera tracking.
*
@@ -1516,6 +1565,9 @@ public class LocationComponentOptions implements Parcelable {
if (this.minZoomIconScale == null) {
missing += " minZoomIconScale";
}
if (this.trackingGesturesManagement == null) {
missing += " trackingGesturesManagement";
}
if (this.trackingInitialMoveThreshold == null) {
missing += " trackingInitialMoveThreshold";
}
@@ -1553,6 +1605,7 @@ public class LocationComponentOptions implements Parcelable {
this.minZoom,
this.maxZoomIconScale,
this.minZoomIconScale,
trackingGesturesManagement,
this.trackingInitialMoveThreshold,
this.trackingMultiFingerMoveThreshold,
this.layerBelow);

View File

@@ -170,6 +170,7 @@
<attr name="mapbox_minZoomIconScale" format="float"/>
<!-- Camera tracking settings -->
<attr name="mapbox_trackingGesturesManagement" format="boolean"/>
<attr name="mapbox_trackingInitialMoveThreshold" format="dimension"/>
<attr name="mapbox_trackingMultiFingerMoveThreshold" format="dimension"/>

View File

@@ -17,5 +17,5 @@
<dimen name="mapbox_minimum_angular_velocity">0.025dp</dimen>
<dimen name="mapbox_locationComponentTrackingInitialMoveThreshold">25dp</dimen>
<dimen name="mapbox_locationComponentTrackingMultiFingerMoveThreshold">125dp</dimen>
<dimen name="mapbox_locationComponentTrackingMultiFingerMoveThreshold">400dp</dimen>
</resources>

View File

@@ -18,7 +18,6 @@
<item name="mapbox_enableStaleState">true</item>
<item name="mapbox_staleStateTimeout">30000</item>
<!-- Location icon padding -->
<item name="mapbox_iconPaddingLeft">0</item>
<item name="mapbox_iconPaddingTop">0</item>
@@ -34,6 +33,7 @@
<item name="mapbox_minZoomIconScale">0.6</item>
<!-- Camera tracking settings -->
<item name="mapbox_trackingGesturesManagement">false</item>
<item name="mapbox_trackingInitialMoveThreshold">@dimen/mapbox_locationComponentTrackingInitialMoveThreshold</item>
<item name="mapbox_trackingMultiFingerMoveThreshold">@dimen/mapbox_locationComponentTrackingMultiFingerMoveThreshold</item>

View File

@@ -15,7 +15,7 @@ import org.mockito.Mockito
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class MapboxAnimatorCoordinatorTest {
class LocationAnimatorCoordinatorTest {
private lateinit var locationAnimatorCoordinator: LocationAnimatorCoordinator
private val cameraPosition: CameraPosition = CameraPosition.DEFAULT

View File

@@ -2,6 +2,7 @@ package com.mapbox.mapboxsdk.location;
import android.graphics.PointF;
import com.mapbox.android.gestures.AndroidGesturesManager;
import com.mapbox.android.gestures.MoveGestureDetector;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.camera.CameraUpdate;
@@ -314,8 +315,9 @@ public class LocationCameraControllerTest {
when(mapboxMap.getUiSettings()).thenReturn(mock(UiSettings.class));
MoveGestureDetector moveGestureDetector = mock(MoveGestureDetector.class);
OnCameraMoveInvalidateListener onCameraMoveInvalidateListener = mock(OnCameraMoveInvalidateListener.class);
AndroidGesturesManager androidGesturesManager = mock(AndroidGesturesManager.class);
return new LocationCameraController(mapboxMap, moveGestureDetector,
onCameraTrackingChangedListener, onCameraMoveInvalidateListener);
onCameraTrackingChangedListener, onCameraMoveInvalidateListener, androidGesturesManager);
}
private LocationCameraController buildCamera(MoveGestureDetector moveGestureDetector) {
@@ -323,15 +325,17 @@ public class LocationCameraControllerTest {
when(mapboxMap.getUiSettings()).thenReturn(mock(UiSettings.class));
OnCameraTrackingChangedListener onCameraTrackingChangedListener = mock(OnCameraTrackingChangedListener.class);
OnCameraMoveInvalidateListener onCameraMoveInvalidateListener = mock(OnCameraMoveInvalidateListener.class);
AndroidGesturesManager androidGesturesManager = mock(AndroidGesturesManager.class);
return new LocationCameraController(mapboxMap, moveGestureDetector,
onCameraTrackingChangedListener, onCameraMoveInvalidateListener);
onCameraTrackingChangedListener, onCameraMoveInvalidateListener, androidGesturesManager);
}
private LocationCameraController buildCamera(MapboxMap mapboxMap) {
MoveGestureDetector moveGestureDetector = mock(MoveGestureDetector.class);
OnCameraTrackingChangedListener onCameraTrackingChangedListener = mock(OnCameraTrackingChangedListener.class);
OnCameraMoveInvalidateListener onCameraMoveInvalidateListener = mock(OnCameraMoveInvalidateListener.class);
AndroidGesturesManager androidGesturesManager = mock(AndroidGesturesManager.class);
return new LocationCameraController(mapboxMap, moveGestureDetector,
onCameraTrackingChangedListener, onCameraMoveInvalidateListener);
onCameraTrackingChangedListener, onCameraMoveInvalidateListener, androidGesturesManager);
}
}

View File

@@ -158,6 +158,12 @@ public class LocationModesActivity extends AppCompatActivity implements OnMapRea
} else if (id == R.id.action_component_enabled) {
activateLocationComponent();
return true;
} else if (id == R.id.action_gestures_management_disabled) {
disableGesturesManagement();
return true;
} else if (id == R.id.action_gestures_management_enabled) {
enableGesturesManagement();
return true;
}
return super.onOptionsItemSelected(item);
@@ -192,18 +198,36 @@ public class LocationModesActivity extends AppCompatActivity implements OnMapRea
}
}
public void toggleStyle() {
private void toggleStyle() {
customStyle = !customStyle;
locationComponent.applyStyle(
this,
customStyle ? R.style.CustomLocationComponent : R.style.mapbox_LocationComponent);
}
public void toggleMapStyle() {
private void toggleMapStyle() {
String styleUrl = mapboxMap.getStyleUrl().contentEquals(Style.DARK) ? Style.LIGHT : Style.DARK;
mapboxMap.setStyle(styleUrl);
}
private void disableGesturesManagement() {
LocationComponentOptions options = locationComponent
.getLocationComponentOptions()
.toBuilder()
.trackingGesturesManagement(false)
.build();
locationComponent.applyStyle(options);
}
private void enableGesturesManagement() {
LocationComponentOptions options = locationComponent
.getLocationComponentOptions()
.toBuilder()
.trackingGesturesManagement(true)
.build();
locationComponent.applyStyle(options);
}
@Override
@SuppressWarnings( {"MissingPermission"})
protected void onStart() {

View File

@@ -10,11 +10,18 @@
app:showAsAction="never"/>
<item android:id="@+id/action_component_disable"
android:title="Disable"
android:title="Disable Component"
app:showAsAction="never"/>
<item android:id="@+id/action_component_enabled"
android:title="Enabled"
android:title="Enable Component"
app:showAsAction="never"/>
<item android:id="@+id/action_gestures_management_enabled"
android:title="Enable Gestures Management"
app:showAsAction="never"/>
<item android:id="@+id/action_gestures_management_disabled"
android:title="Disable Gestures Management"
app:showAsAction="never"/>
</menu>