diff --git a/CHANGELOG.md b/CHANGELOG.md
index 683f6f911..26a8f6168 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -31,6 +31,8 @@
- [core] Fix memory access violation exception in vector_tile_data.cpp [#632](https://github.com/maplibre/maplibre-native/pull/632)
- [iOS] Fix a bug where the compass was determined to be misplaced when hidden [#498](https://github.com/maplibre/maplibre-native/pull/498).
- [core] `MaptilerFileSource` renamed to `MBTilesFileSource` [#198](https://github.com/maplibre/maplibre-native/pull/198).
+- [android] Fix `OnMoveListener::onMoveBegin` was always called even on a simple tap. [#2792](https://github.com/maplibre/maplibre-native/issues/2792)
+ In case you rely on the old behavior, call `maplibreMap.getGesturesManager().getMoveGestureDetector().setMoveThreshold(0f)` to restore it
## maps-v1.6.0
diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationCameraController.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationCameraController.java
index 4b82ff8d4..c92330807 100644
--- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationCameraController.java
+++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationCameraController.java
@@ -314,7 +314,8 @@ final class LocationCameraController {
if (isLocationTracking()) {
moveGestureDetector.setMoveThreshold(options.trackingInitialMoveThreshold());
} else {
- moveGestureDetector.setMoveThreshold(0f);
+ float initialMoveThreshold = initialGesturesManager.getMoveGestureDetector().getMoveThreshold();
+ moveGestureDetector.setMoveThreshold(initialMoveThreshold);
moveGestureDetector.setMoveThresholdRect(null);
}
}
diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapGestureDetector.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapGestureDetector.java
index af36ccbd2..4d04e18bd 100644
--- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapGestureDetector.java
+++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapGestureDetector.java
@@ -169,8 +169,15 @@ final class MapGestureDetector {
androidGesturesManager.setMutuallyExclusiveGestures(shoveScaleSet, shoveRotateSet, ScaleLongPressSet);
}
+ // If this was 0°, every shove gesture (for tilting the map) would be detected as also a rotate
+ androidGesturesManager.getRotateGestureDetector().setAngleThreshold(3f);
+
+ // If this was 0 (the default), a simple tap would also be detected as a move. A (very) small
+ // move threshold solves this issue, while not making the map feel "sticky". (See #2792)
+ androidGesturesManager.getMoveGestureDetector().setMoveThresholdResource(R.dimen.maplibre_minimum_move_threshold);
+
gesturesManager = androidGesturesManager;
- gesturesManager.getRotateGestureDetector().setAngleThreshold(3f);
+
}
/**
diff --git a/platform/android/MapLibreAndroid/src/main/res/values/dimens.xml b/platform/android/MapLibreAndroid/src/main/res/values/dimens.xml
index 6252cbf68..11b8aa1ee 100644
--- a/platform/android/MapLibreAndroid/src/main/res/values/dimens.xml
+++ b/platform/android/MapLibreAndroid/src/main/res/values/dimens.xml
@@ -24,6 +24,10 @@
0.29dp
+
+ 4dp
+
+
25dp
400dp
diff --git a/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/location/LocationCameraControllerTest.kt b/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/location/LocationCameraControllerTest.kt
index f675f01d1..bc4dfad78 100644
--- a/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/location/LocationCameraControllerTest.kt
+++ b/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/location/LocationCameraControllerTest.kt
@@ -94,7 +94,7 @@ class LocationCameraControllerTest {
camera.initializeOptions(options)
camera.cameraMode = CameraMode.NONE
Mockito.verify(moveGestureDetector, Mockito.times(2)).moveThreshold =
- 0f // one for initialization
+ MOVEMENT_THRESHOLD // one for initialization
Mockito.verify(moveGestureDetector, Mockito.times(2)).moveThresholdRect =
null // one for initialization
}
@@ -698,9 +698,7 @@ class LocationCameraControllerTest {
@Test
fun gesturesManagement_enabled() {
val maplibreMap = Mockito.mock(MapLibreMap::class.java)
- val initialGesturesManager = Mockito.mock(
- AndroidGesturesManager::class.java
- )
+ val initialGesturesManager = buildInitialGesturesManager()
val internalGesturesManager = Mockito.mock(
AndroidGesturesManager::class.java
)
@@ -717,9 +715,7 @@ class LocationCameraControllerTest {
@Test
fun gesturesManagement_disabled() {
val maplibreMap = Mockito.mock(MapLibreMap::class.java)
- val initialGesturesManager = Mockito.mock(
- AndroidGesturesManager::class.java
- )
+ val initialGesturesManager = buildInitialGesturesManager()
val internalGesturesManager = Mockito.mock(
AndroidGesturesManager::class.java
)
@@ -736,9 +732,7 @@ class LocationCameraControllerTest {
@Test
fun gesturesManagement_optionNotChangedInitial() {
val maplibreMap = Mockito.mock(MapLibreMap::class.java)
- val initialGesturesManager = Mockito.mock(
- AndroidGesturesManager::class.java
- )
+ val initialGesturesManager = buildInitialGesturesManager()
val internalGesturesManager = Mockito.mock(
AndroidGesturesManager::class.java
)
@@ -756,9 +750,7 @@ class LocationCameraControllerTest {
@Test
fun gesturesManagement_optionNotChangedInternal() {
val maplibreMap = Mockito.mock(MapLibreMap::class.java)
- val initialGesturesManager = Mockito.mock(
- AndroidGesturesManager::class.java
- )
+ val initialGesturesManager = buildInitialGesturesManager()
val internalGesturesManager = Mockito.mock(
AndroidGesturesManager::class.java
)
@@ -792,7 +784,7 @@ class LocationCameraControllerTest {
Mockito.`when`(options.trackingMultiFingerProtectedMoveArea()).thenReturn(multiFingerArea)
camera.initializeOptions(options)
camera.onMoveListener.onMoveBegin(moveGestureDetector)
- Mockito.verify(moveGestureDetector, Mockito.times(2)).moveThreshold = 0f
+ Mockito.verify(moveGestureDetector, Mockito.times(2)).moveThreshold = MOVEMENT_THRESHOLD
Mockito.verify(moveGestureDetector, Mockito.times(2)).moveThresholdRect = null
}
@@ -876,12 +868,8 @@ class LocationCameraControllerTest {
Mockito.verify(moveGestureDetector, Mockito.times(2)).interrupt()
// verify that threshold are reset
- val moveThresholdCaptor = ArgumentCaptor.forClass(
- Float::class.java
- )
Mockito.verify(moveGestureDetector, Mockito.atLeastOnce()).moveThreshold =
- moveThresholdCaptor.capture()
- org.junit.Assert.assertEquals(java.lang.Float.valueOf(0f), moveThresholdCaptor.value)
+ MOVEMENT_THRESHOLD
}
@Test
@@ -973,18 +961,9 @@ class LocationCameraControllerTest {
Mockito.verify(moveGestureDetector, Mockito.times(2)).interrupt()
// verify that threshold are reset
- val moveThresholdCaptor = ArgumentCaptor.forClass(
- Float::class.java
- )
Mockito.verify(moveGestureDetector, Mockito.atLeastOnce()).moveThreshold =
- moveThresholdCaptor.capture()
- org.junit.Assert.assertEquals(java.lang.Float.valueOf(0f), moveThresholdCaptor.value)
- val areaCaptor = ArgumentCaptor.forClass(
- RectF::class.java
- )
- Mockito.verify(moveGestureDetector, Mockito.atLeastOnce()).moveThresholdRect =
- areaCaptor.capture()
- org.junit.Assert.assertNull(areaCaptor.value)
+ MOVEMENT_THRESHOLD
+ Mockito.verify(moveGestureDetector, Mockito.atLeastOnce()).moveThresholdRect = null
}
@Test
@@ -1911,9 +1890,6 @@ class LocationCameraControllerTest {
val onCameraMoveInvalidateListener = Mockito.mock(
OnCameraMoveInvalidateListener::class.java
)
- val initialGesturesManager = Mockito.mock(
- AndroidGesturesManager::class.java
- )
val internalGesturesManager = Mockito.mock(
AndroidGesturesManager::class.java
)
@@ -1923,7 +1899,7 @@ class LocationCameraControllerTest {
moveGestureDetector,
onCameraTrackingChangedListener,
onCameraMoveInvalidateListener,
- initialGesturesManager,
+ buildInitialGesturesManager(),
internalGesturesManager
)
}
@@ -1955,9 +1931,6 @@ class LocationCameraControllerTest {
val onCameraMoveInvalidateListener = Mockito.mock(
OnCameraMoveInvalidateListener::class.java
)
- val initialGesturesManager = Mockito.mock(
- AndroidGesturesManager::class.java
- )
val internalGesturesManager = Mockito.mock(
AndroidGesturesManager::class.java
)
@@ -1967,7 +1940,7 @@ class LocationCameraControllerTest {
moveGestureDetector,
onCameraTrackingChangedListener,
onCameraMoveInvalidateListener,
- initialGesturesManager,
+ buildInitialGesturesManager(),
internalGesturesManager
)
}
@@ -1985,9 +1958,6 @@ class LocationCameraControllerTest {
val onCameraMoveInvalidateListener = Mockito.mock(
OnCameraMoveInvalidateListener::class.java
)
- val initialGesturesManager = Mockito.mock(
- AndroidGesturesManager::class.java
- )
val internalGesturesManager = Mockito.mock(
AndroidGesturesManager::class.java
)
@@ -1997,7 +1967,7 @@ class LocationCameraControllerTest {
moveGestureDetector,
onCameraTrackingChangedListener,
onCameraMoveInvalidateListener,
- initialGesturesManager,
+ buildInitialGesturesManager(),
internalGesturesManager
)
}
@@ -2034,9 +2004,6 @@ class LocationCameraControllerTest {
val onCameraMoveInvalidateListener = Mockito.mock(
OnCameraMoveInvalidateListener::class.java
)
- val initialGesturesManager = Mockito.mock(
- AndroidGesturesManager::class.java
- )
val internalGesturesManager = Mockito.mock(
AndroidGesturesManager::class.java
)
@@ -2046,7 +2013,7 @@ class LocationCameraControllerTest {
moveGestureDetector,
onCameraTrackingChangedListener,
onCameraMoveInvalidateListener,
- initialGesturesManager,
+ buildInitialGesturesManager(),
internalGesturesManager
)
}
@@ -2066,9 +2033,6 @@ class LocationCameraControllerTest {
val onCameraMoveInvalidateListener = Mockito.mock(
OnCameraMoveInvalidateListener::class.java
)
- val initialGesturesManager = Mockito.mock(
- AndroidGesturesManager::class.java
- )
val internalGesturesManager = Mockito.mock(
AndroidGesturesManager::class.java
)
@@ -2078,7 +2042,7 @@ class LocationCameraControllerTest {
moveGestureDetector,
onCameraTrackingChangedListener,
onCameraMoveInvalidateListener,
- initialGesturesManager,
+ buildInitialGesturesManager(),
internalGesturesManager
)
locationCameraController.setEnabled(true)
@@ -2113,6 +2077,18 @@ class LocationCameraControllerTest {
)
}
+ private fun buildInitialGesturesManager(): AndroidGesturesManager {
+ val moveGestureDetector = Mockito.mock()
+ // return just "some" value
+ Mockito.`when`(moveGestureDetector.moveThreshold).thenReturn(MOVEMENT_THRESHOLD)
+
+ val manager = Mockito.mock()
+ Mockito.`when`(manager.moveGestureDetector).thenReturn(moveGestureDetector)
+ return manager
+ }
+
+ private val MOVEMENT_THRESHOLD = 10f
+
private fun getAnimationListener(
@MapLibreAnimator.Type animatorType: Int,
holders: Set