Skip to content

Commit 4b8f29e

Browse files
committed
Implement draggable markers
1 parent 8e8858b commit 4b8f29e

4 files changed

Lines changed: 149 additions & 9 deletions

File tree

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,26 @@ import android.graphics.Bitmap
1212
/**
1313
* Represents a marker on the map at a specific geographic location.
1414
*
15-
* @property position The geographic location of the marker
15+
* @property position The geographic location of the marker (mutable for dragging)
1616
* @property title Optional title text displayed when marker is clicked
1717
* @property snippet Optional snippet text displayed below the title
1818
* @property icon Custom icon bitmap. If null, a default marker icon will be used
1919
* @property anchor Anchor point for the marker icon. Default (0.5f, 1.0f) means
2020
* the marker is centered horizontally and anchored at the bottom
2121
* @property visible Whether the marker is visible. Default is true
2222
* @property alpha Opacity of the marker from 0.0 (transparent) to 1.0 (opaque). Default is 1.0
23+
* @property draggable Whether the marker can be dragged. Default is false
2324
* @property tag Optional user data associated with the marker
2425
*/
2526
data class Marker(
26-
val position: LatLng,
27+
var position: LatLng,
2728
val title: String? = null,
2829
val snippet: String? = null,
2930
val icon: Bitmap? = null,
3031
val anchor: Pair<Float, Float> = Pair(0.5f, 1.0f),
3132
val visible: Boolean = true,
3233
val alpha: Float = 1.0f,
34+
val draggable: Boolean = false,
3335
val tag: Any? = null,
3436
) {
3537
init {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
* Interface for receiving marker drag events.
12+
*
13+
* Compatible with Google Maps API. Implement this interface to receive
14+
* callbacks when a draggable marker is dragged across the map.
15+
*/
16+
interface OnMarkerDragListener {
17+
/**
18+
* Called when a marker starts being dragged.
19+
*
20+
* @param marker The marker being dragged
21+
*/
22+
fun onMarkerDragStart(marker: Marker)
23+
24+
/**
25+
* Called repeatedly while a marker is being dragged.
26+
*
27+
* @param marker The marker being dragged
28+
*/
29+
fun onMarkerDrag(marker: Marker)
30+
31+
/**
32+
* Called when a marker has finished being dragged.
33+
*
34+
* @param marker The marker that was dragged
35+
*/
36+
fun onMarkerDragEnd(marker: Marker)
37+
}

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

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ class OpenMapView
5151
private var lastTouchY = 0f
5252
private var onMapClickListener: OnMapClickListener? = null
5353
private var onMapLongClickListener: OnMapLongClickListener? = null
54+
private var onMarkerDragListener: OnMarkerDragListener? = null
55+
private var draggedMarker: Marker? = null
56+
private var isDragging = false
5457

5558
private val gestureDetector =
5659
GestureDetector(
@@ -103,30 +106,76 @@ class OpenMapView
103106
}
104107

105108
override fun onTouchEvent(event: MotionEvent): Boolean {
106-
// Let gesture detector handle long press
107-
gestureDetector.onTouchEvent(event)
109+
// Let gesture detector handle long press (but not during drag)
110+
if (!isDragging) {
111+
gestureDetector.onTouchEvent(event)
112+
}
108113

109114
// Let scale detector handle pinch gestures
110115
scaleGestureDetector.onTouchEvent(event)
111116

112-
// Handle panning only if not scaling
117+
// Handle dragging and panning only if not scaling
113118
if (!scaleGestureDetector.isInProgress) {
114119
when (event.action) {
115120
MotionEvent.ACTION_DOWN -> {
116121
lastTouchX = event.x
117122
lastTouchY = event.y
123+
// Check if touch is on a draggable marker
124+
val touchedMarker = controller.handleMarkerTouch(event.x, event.y)
125+
if (touchedMarker != null && touchedMarker.draggable) {
126+
draggedMarker = touchedMarker
127+
isDragging = false
128+
}
118129
return true
119130
}
120131
MotionEvent.ACTION_MOVE -> {
121132
val dx = event.x - lastTouchX
122133
val dy = event.y - lastTouchY
123-
controller.updatePanOffset(dx, dy)
124-
lastTouchX = event.x
125-
lastTouchY = event.y
126-
invalidate()
134+
val movementDistance = kotlin.math.sqrt((dx * dx + dy * dy).toDouble())
135+
136+
if (draggedMarker != null) {
137+
// Start dragging if moved more than threshold
138+
if (!isDragging && movementDistance > 10) {
139+
isDragging = true
140+
controller.commitPan()
141+
onMarkerDragListener?.onMarkerDragStart(draggedMarker!!)
142+
}
143+
144+
// Continue dragging
145+
if (isDragging) {
146+
val latLng = controller.screenToLatLng(event.x, event.y)
147+
draggedMarker!!.position = latLng
148+
onMarkerDragListener?.onMarkerDrag(draggedMarker!!)
149+
invalidate()
150+
lastTouchX = event.x
151+
lastTouchY = event.y
152+
return true
153+
}
154+
}
155+
156+
// Pan the map if not dragging a marker
157+
if (!isDragging) {
158+
controller.updatePanOffset(dx, dy)
159+
lastTouchX = event.x
160+
lastTouchY = event.y
161+
invalidate()
162+
}
127163
return true
128164
}
129165
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
166+
// Handle drag end
167+
if (isDragging && draggedMarker != null) {
168+
onMarkerDragListener?.onMarkerDragEnd(draggedMarker!!)
169+
draggedMarker = null
170+
isDragging = false
171+
invalidate()
172+
return true
173+
}
174+
175+
// Reset drag state
176+
draggedMarker = null
177+
isDragging = false
178+
130179
// Check if touch is on a marker (only if there was minimal movement)
131180
val dx = event.x - lastTouchX
132181
val dy = event.y - lastTouchY
@@ -404,6 +453,35 @@ class OpenMapView
404453
controller.onMarkerClickListener = listener
405454
}
406455

456+
/**
457+
* Sets a listener to handle marker drag events.
458+
*
459+
* Called when a draggable marker is dragged by the user. The marker must have
460+
* its draggable property set to true to receive drag events.
461+
*
462+
* Example:
463+
* ```kotlin
464+
* mapView.setOnMarkerDragListener(object : OnMarkerDragListener {
465+
* override fun onMarkerDragStart(marker: Marker) {
466+
* Log.d("Map", "Drag started: ${marker.title}")
467+
* }
468+
*
469+
* override fun onMarkerDrag(marker: Marker) {
470+
* Log.d("Map", "Dragging: ${marker.position}")
471+
* }
472+
*
473+
* override fun onMarkerDragEnd(marker: Marker) {
474+
* Log.d("Map", "Drag ended: ${marker.position}")
475+
* }
476+
* })
477+
* ```
478+
*
479+
* @param listener The listener to receive drag events, or null to clear the listener
480+
*/
481+
fun setOnMarkerDragListener(listener: OnMarkerDragListener?) {
482+
onMarkerDragListener = listener
483+
}
484+
407485
/**
408486
* Sets a listener to handle map click events.
409487
*

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,4 +156,27 @@ class MarkerTest {
156156
fun testMarkerAlpha_InvalidAboveOne() {
157157
Marker(position = LatLng(0.0, 0.0), alpha = 1.1f)
158158
}
159+
160+
@Test
161+
fun testMarkerDraggable_Default() {
162+
val marker = Marker(position = LatLng(0.0, 0.0))
163+
assertEquals(false, marker.draggable)
164+
}
165+
166+
@Test
167+
fun testMarkerDraggable_SetToTrue() {
168+
val marker = Marker(position = LatLng(0.0, 0.0), draggable = true)
169+
assertEquals(true, marker.draggable)
170+
}
171+
172+
@Test
173+
fun testMarkerPosition_Mutable() {
174+
val marker = Marker(position = LatLng(0.0, 0.0))
175+
assertEquals(0.0, marker.position.latitude, 0.001)
176+
assertEquals(0.0, marker.position.longitude, 0.001)
177+
178+
marker.position = LatLng(10.0, 20.0)
179+
assertEquals(10.0, marker.position.latitude, 0.001)
180+
assertEquals(20.0, marker.position.longitude, 0.001)
181+
}
159182
}

0 commit comments

Comments
 (0)