From 2e0b464849680a0014ade90b5f6adb7734b0f988 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 23 Jun 2025 18:36:33 +0200 Subject: [PATCH] set default move gesture threshold to a small non-zero value (#3573) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- CHANGELOG.md | 2 + .../location/LocationCameraController.java | 3 +- .../android/maps/MapGestureDetector.java | 9 ++- .../src/main/res/values/dimens.xml | 4 + .../location/LocationCameraControllerTest.kt | 76 +++++++------------ 5 files changed, 42 insertions(+), 52 deletions(-) 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