Skip to content

Commit 30f13c5

Browse files
committed
Add OnMarkerClickListener
1 parent 8fc9e6c commit 30f13c5

File tree

5 files changed

+88
-23
lines changed

5 files changed

+88
-23
lines changed

docs/PUBLIC_API.md

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -182,31 +182,66 @@ Methods available on the UiSettings object returned by `getUiSettings()`:
182182

183183
## GoogleMap Class - Event Listeners
184184

185+
OpenMapView provides comprehensive event listener support using Kotlin `fun interface` for single-method listeners (enabling lambda syntax) and regular `interface` for multi-method listeners. All callbacks execute on the UI thread.
186+
185187
| Method | Return Type | Status | Notes |
186188
| --------------------------------------------------------------------- | ---------------- | --------------- | ---------------------------------------------------- |
187-
| `setOnMapClickListener(OnMapClickListener)` | `void` | IMPLEMENTED | Full implementation with LatLng coordinate callbacks |
188-
| `setOnMapLongClickListener(OnMapLongClickListener)` | `void` | IMPLEMENTED | GestureDetector-based long-press detection |
189-
| `setOnMarkerClickListener(OnMarkerClickListener)` | `void` | IMPLEMENTED | Returns boolean to consume event |
190-
| `setOnMarkerDragListener(OnMarkerDragListener)` | `void` | IMPLEMENTED | Full drag support with start/drag/end callbacks |
191-
| `setOnPolylineClickListener(OnPolylineClickListener)` | `void` | IMPLEMENTED | Point-to-line distance hit testing with tolerance |
192-
| `setOnPolygonClickListener(OnPolygonClickListener)` | `void` | IMPLEMENTED | Ray casting algorithm with hole support |
193-
| `setOnCircleClickListener(OnCircleClickListener)` | `void` | IMPLEMENTED | Distance-based hit testing with stroke width |
194-
| `setOnGroundOverlayClickListener(OnGroundOverlayClickListener)` | `void` | IMPLEMENTED | Rectangle hit testing with rotation support |
189+
| `setOnMapClickListener(OnMapClickListener)` | `void` | IMPLEMENTED | `fun interface` - LatLng coordinate callbacks. Fires when map clicked (not markers/shapes) |
190+
| `setOnMapLongClickListener(OnMapLongClickListener)` | `void` | IMPLEMENTED | `fun interface` - GestureDetector-based long-press detection (~500ms) |
191+
| `setOnMarkerClickListener(OnMarkerClickListener)` | `void` | IMPLEMENTED | `fun interface` - Returns boolean to consume event (true = no info window/centering) |
192+
| `setOnMarkerDragListener(OnMarkerDragListener)` | `void` | IMPLEMENTED | 3-method interface - onMarkerDragStart/onMarkerDrag/onMarkerDragEnd callbacks |
193+
| `setOnPolylineClickListener(OnPolylineClickListener)` | `void` | IMPLEMENTED | `fun interface` - Point-to-line distance hit testing (~10px tolerance) |
194+
| `setOnPolygonClickListener(OnPolygonClickListener)` | `void` | IMPLEMENTED | `fun interface` - Ray casting algorithm with hole support |
195+
| `setOnCircleClickListener(OnCircleClickListener)` | `void` | IMPLEMENTED | `fun interface` - Distance-based hit testing (center to touch point) |
196+
| `setOnGroundOverlayClickListener(OnGroundOverlayClickListener)` | `void` | IMPLEMENTED | `fun interface` - Rectangle bounds hit testing with rotation support |
195197
| `setOnPoiClickListener(OnPoiClickListener)` | `void` | NOT PLANNED | POI data not available in OSM tiles |
196-
| `setOnCameraMoveStartedListener(OnCameraMoveStartedListener)` | `void` | IMPLEMENTED | Tracks gesture, API, and developer-initiated moves |
197-
| `setOnCameraMoveListener(OnCameraMoveListener)` | `void` | IMPLEMENTED | Called repeatedly during camera movement |
198-
| `setOnCameraIdleListener(OnCameraIdleListener)` | `void` | IMPLEMENTED | Called when camera stops moving |
199-
| `setOnCameraMoveCanceledListener(OnCameraMoveCanceledListener)` | `void` | IMPLEMENTED | Called when animation is interrupted |
198+
| `setOnCameraMoveStartedListener(OnCameraMoveStartedListener)` | `void` | IMPLEMENTED | `fun interface` - Reason constants: REASON_GESTURE (1), REASON_API_ANIMATION (2), REASON_DEVELOPER_ANIMATION (3) |
199+
| `setOnCameraMoveListener(OnCameraMoveListener)` | `void` | IMPLEMENTED | `fun interface` - Called repeatedly during camera movement (60fps) - keep lightweight! |
200+
| `setOnCameraIdleListener(OnCameraIdleListener)` | `void` | IMPLEMENTED | `fun interface` - Called when camera stops moving (after gesture or animation completes) |
201+
| `setOnCameraMoveCanceledListener(OnCameraMoveCanceledListener)` | `void` | IMPLEMENTED | `fun interface` - Called when animation interrupted by gesture or new camera command |
200202
| `setOnMapLoadedCallback(OnMapLoadedCallback)` | `void` | NOT IMPLEMENTED | Tiles load asynchronously, callback could be added |
201203
| `setInfoWindowAdapter(InfoWindowAdapter)` | `void` | NOT IMPLEMENTED | Custom adapters not yet implemented |
202-
| `setOnInfoWindowClickListener(OnInfoWindowClickListener)` | `void` | IMPLEMENTED | Full support with basic info window rendering |
204+
| `setOnInfoWindowClickListener(OnInfoWindowClickListener)` | `void` | IMPLEMENTED | `fun interface` - Full support with basic info window rendering |
203205
| `setOnInfoWindowCloseListener(OnInfoWindowCloseListener)` | `void` | NOT IMPLEMENTED | Not applicable |
204206
| `setOnInfoWindowLongClickListener(OnInfoWindowLongClickListener)` | `void` | NOT IMPLEMENTED | Not applicable |
205207
| `setOnMyLocationButtonClickListener(OnMyLocationButtonClickListener)` | `void` | NOT IMPLEMENTED | Not applicable |
206208
| `setOnMyLocationClickListener(OnMyLocationClickListener)` | `void` | NOT IMPLEMENTED | Not applicable |
207209
| `setOnIndoorStateChangeListener(OnIndoorStateChangeListener)` | `void` | NOT PLANNED | Indoor mapping not supported |
208210
| `getFocusedBuilding()` | `IndoorBuilding` | NOT PLANNED | Indoor mapping not supported |
209211

212+
**Event Priority (highest to lowest):**
213+
1. Info window click (if info window is showing)
214+
2. Marker click (returns boolean - true consumes event)
215+
3. Ground overlay click (highest z-index first)
216+
4. Circle click (highest z-index first)
217+
5. Polygon click (highest z-index first)
218+
6. Polyline click (highest z-index first)
219+
7. Map click (fires if nothing else consumed event)
220+
221+
**Camera Event Sequence:**
222+
- **Successful animation:** onCameraMoveStarted(reason) → onCameraMove* → onCameraIdle
223+
- **Interrupted animation:** onCameraMoveStarted(reason) → onCameraMove* → onCameraMoveCanceled → onCameraMoveStarted(REASON_GESTURE) → ...
224+
225+
**Kotlin Lambda Syntax Examples:**
226+
```kotlin
227+
// Single-method listeners support lambda syntax
228+
mapView.setOnMapClickListener { latLng ->
229+
Log.d("Map", "Clicked: ${latLng.latitude}, ${latLng.longitude}")
230+
}
231+
232+
mapView.setOnMarkerClickListener { marker ->
233+
Toast.makeText(context, marker.title, Toast.LENGTH_SHORT).show()
234+
true // Consume event (prevents info window)
235+
}
236+
237+
// Multi-method listeners use object syntax
238+
mapView.setOnMarkerDragListener(object : OnMarkerDragListener {
239+
override fun onMarkerDragStart(marker: Marker) { }
240+
override fun onMarkerDrag(marker: Marker) { }
241+
override fun onMarkerDragEnd(marker: Marker) { }
242+
})
243+
```
244+
210245
---
211246

212247
## GeoJSON Support
@@ -278,6 +313,11 @@ This method is an OpenMapView-specific feature not present in Google Maps API. S
278313
- Vector shapes: 100% (polylines, polygons with holes, circles, visibility, click listeners, z-index)
279314
- Ground overlays: 100% (position/bounds modes, rotation, transparency, click listener)
280315
- Tile overlays: 100% (custom tile providers, transparency, z-index, visibility, predefined OSM providers)
316+
- **Event listeners: 100%** (13/13 applicable listeners - all use Kotlin `fun interface` or regular interface)
317+
- Map events: OnMapClickListener, OnMapLongClickListener
318+
- Marker events: OnMarkerClickListener, OnMarkerDragListener, OnInfoWindowClickListener
319+
- Shape events: OnPolylineClickListener, OnPolygonClickListener, OnCircleClickListener, OnGroundOverlayClickListener
320+
- Camera events: OnCameraMoveStartedListener, OnCameraMoveListener, OnCameraIdleListener, OnCameraMoveCanceledListener
281321
- Map interaction: 100% (click listeners, long-click, projection API, visible region)
282322
- Zoom control: 100% (min/max zoom preferences, getZoom, built-in zoom controls)
283323
- Map types: 100% (5 types: NONE, NORMAL, TERRAIN, HUMANITARIAN, CYCLE)

openmapview/src/main/kotlin/de/afarber/openmapview/MapController.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class MapController(
7878

7979
private val markers = mutableListOf<Marker>()
8080
private val defaultMarkerIcon by lazy { MarkerIconFactory.getDefaultIcon() }
81-
var onMarkerClickListener: ((Marker) -> Boolean)? = null
81+
var onMarkerClickListener: OnMarkerClickListener? = null
8282
var onInfoWindowClickListener: OnInfoWindowClickListener? = null
8383

8484
private val polylines = mutableListOf<Polyline>()
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (c) 2025 Alexander Farber
3+
* SPDX-License-Identifier: MIT
4+
*
5+
* This file is part of the OpenMapView project (https://github.com/afarber/OpenMapView)
6+
*/
7+
8+
package de.afarber.openmapview
9+
10+
/**
11+
* Listener interface for marker click events.
12+
*
13+
* Called when the user taps on a marker.
14+
*/
15+
fun interface OnMarkerClickListener {
16+
/**
17+
* Called when a marker is clicked.
18+
*
19+
* @param marker The marker that was clicked
20+
* @return true to consume the event (prevents default behavior like showing info window),
21+
* false to allow default behavior
22+
*/
23+
fun onMarkerClick(marker: Marker): Boolean
24+
}

openmapview/src/main/kotlin/de/afarber/openmapview/OpenMapView.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ class OpenMapView
268268
touchedMarker.showInfoWindow()
269269
}
270270

271-
val consumed = controller.onMarkerClickListener?.invoke(touchedMarker) ?: false
271+
val consumed = controller.onMarkerClickListener?.onMarkerClick(touchedMarker) ?: false
272272
controller.commitPan()
273273
invalidate()
274274
return true
@@ -922,9 +922,9 @@ class OpenMapView
922922
* }
923923
* ```
924924
*
925-
* @param listener Callback invoked when a marker is clicked. Return true to consume the event.
925+
* @param listener Callback invoked when a marker is clicked. Return true to consume the event, or null to clear the listener.
926926
*/
927-
fun setOnMarkerClickListener(listener: (Marker) -> Boolean) {
927+
fun setOnMarkerClickListener(listener: OnMarkerClickListener?) {
928928
controller.onMarkerClickListener = listener
929929
}
930930

openmapview/src/test/kotlin/de/afarber/openmapview/MapControllerTest.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -237,17 +237,18 @@ class MapControllerTest {
237237
var callbackInvoked = false
238238
var clickedMarker: Marker? = null
239239

240-
controller.onMarkerClickListener = { marker ->
241-
callbackInvoked = true
242-
clickedMarker = marker
243-
true
244-
}
240+
controller.onMarkerClickListener =
241+
OnMarkerClickListener { marker ->
242+
callbackInvoked = true
243+
clickedMarker = marker
244+
true
245+
}
245246

246247
val marker = Marker(LatLng(51.4661, 7.2491))
247248
controller.addMarker(marker)
248249

249250
// Simulate marker click by calling the listener
250-
val result = controller.onMarkerClickListener?.invoke(marker)
251+
val result = controller.onMarkerClickListener?.onMarkerClick(marker)
251252

252253
assertTrue(callbackInvoked)
253254
assertEquals(marker, clickedMarker)

0 commit comments

Comments
 (0)