Skip to content

Commit 3a89c24

Browse files
pjleonard37github-actions[bot]
authored andcommitted
Add tap gesture support to Marker components (#6786)
Adds tap (click) gesture support to Marker components across both iOS and Android platforms, providing API parity and consistent user interaction patterns. To keep the Markers feature simple I've only added support for Tap (Click) )gestures, but it would be trivial to add other gesture types if needed. iOS Implementation: - Add `onTapGesture` modifier to Marker component with callback support - Update SwiftUI MarkersExample to demonstrate interactive markers with color changes Android Implementation: - Add `onClick` parameter to Marker composable with optional callback function - Update MarkersActivity to showcase tap functionality with random color transitions GitOrigin-RevId: eadd595cdec6c886a7ceca08a688bfd48afb8768
1 parent 8297942 commit 3a89c24

File tree

6 files changed

+95
-12
lines changed

6 files changed

+95
-12
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ Mapbox welcomes participation and contributions from everyone.
77
# main
88
## Features ✨ and improvements 🏁
99
* Added experimental `MapOptions.Builder.scaleFactor()` for scaling icons and texts.
10+
* Add click gesture support to `Marker` composable with `onClick` parameter
11+
12+
## Bug fixes 🐞
1013
* Introduced `ScaleBarSettings.distanceUnits` property supporting metric, imperial, and nautical units, replacing the boolean `isMetricUnits` property.
1114

1215
# 11.16.0-rc.2 October 14, 2025

compose-app/src/main/java/com/mapbox/maps/compose/testapp/examples/annotation/MarkersActivity.kt

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public class MarkersActivity : ComponentActivity() {
4747
override fun onCreate(savedInstanceState: Bundle?) {
4848
super.onCreate(savedInstanceState)
4949
setContent {
50-
val tappedPoints = remember { mutableStateListOf<Point>() }
50+
val clickedPoints = remember { mutableStateListOf<Point>() }
5151
var markerColor by remember { mutableStateOf(Color(0xffcfdaf7)) }
5252
var strokeColor by remember { mutableStateOf(Color(0xff3a59fa)) }
5353
var showStroke by remember { mutableStateOf(true) }
@@ -81,11 +81,11 @@ public class MarkersActivity : ComponentActivity() {
8181
MapboxStandardStyle(
8282
standardStyleState = rememberStandardStyleState {
8383
interactionsState.onMapClicked { context ->
84-
tappedPoints.add(context.coordinateInfo.coordinate)
84+
clickedPoints.add(context.coordinateInfo.coordinate)
8585
return@onMapClicked true
8686
}
8787
interactionsState.onMapLongClicked { _ ->
88-
tappedPoints.clear()
88+
clickedPoints.clear()
8989
return@onMapLongClicked true
9090
}
9191
}
@@ -96,14 +96,20 @@ public class MarkersActivity : ComponentActivity() {
9696
point = HELSINKI,
9797
color = markerColor,
9898
stroke = if (showStroke) strokeColor else null,
99-
text = if (showText) "Central Helsinki" else null
99+
text = if (showText) "Central Helsinki" else null,
100+
onClick = {
101+
markerColor = getRandomColor()
102+
}
100103
)
101-
tappedPoints.forEach { it ->
104+
clickedPoints.forEach { it ->
102105
Marker(
103106
point = it,
104107
color = markerColor,
105108
stroke = if (showStroke) strokeColor else null,
106-
text = if (showText) String.format("%.3f, %.3f", it.latitude(), it.longitude()) else null
109+
text = if (showText) String.format("%.3f, %.3f", it.latitude(), it.longitude()) else null,
110+
onClick = {
111+
markerColor = getRandomColor()
112+
}
107113
)
108114
}
109115
}
@@ -144,7 +150,7 @@ private fun SelectionBox(
144150
horizontalAlignment = Alignment.CenterHorizontally
145151
) {
146152
Text(
147-
text = "Tap map to add a Marker",
153+
text = "Click to add a marker or change marker color",
148154
color = Color.White,
149155
modifier = Modifier.padding(bottom = 4.dp)
150156
)
@@ -238,4 +244,11 @@ private fun SelectionBox(
238244
private val colorOptions = listOf(
239245
Color.Red, Color.Green, Color(0xff4264fb), Color.Yellow, Color.Magenta, Color.Cyan,
240246
Color.Black, Color.White, Color.Gray, Color(0xff0f38bf)
241-
)
247+
)
248+
249+
/**
250+
* Get a random color from the available color options
251+
*/
252+
private fun getRandomColor(): Color {
253+
return colorOptions.random()
254+
}

extension-compose/api/Release/metalava.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ package com.mapbox.maps.extension.compose.annotation {
173173
}
174174

175175
public final class MarkerKt {
176-
method @androidx.compose.runtime.Composable @com.mapbox.maps.MapboxExperimental @com.mapbox.maps.extension.compose.MapboxMapComposable public static void Marker(com.mapbox.geojson.Point point, long color = Color(4291812087), long innerColor = Color(4294967295), androidx.compose.ui.graphics.Color? stroke = Color(4282014202), String? text = null);
176+
method @androidx.compose.runtime.Composable @com.mapbox.maps.MapboxExperimental @com.mapbox.maps.extension.compose.MapboxMapComposable public static void Marker(com.mapbox.geojson.Point point, long color = Color(4291812087), long innerColor = Color(4294967295), androidx.compose.ui.graphics.Color? stroke = Color(4282014202), String? text = null, kotlin.jvm.functions.Function0<kotlin.Unit>? onClick = null);
177177
}
178178

179179
public final class ViewAnnotationKt {

extension-compose/api/extension-compose.api

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ public final class com/mapbox/maps/extension/compose/annotation/IconImageKt {
197197
}
198198

199199
public final class com/mapbox/maps/extension/compose/annotation/MarkerKt {
200-
public static final fun Marker-YiRMAXg (Lcom/mapbox/geojson/Point;JJLandroidx/compose/ui/graphics/Color;Ljava/lang/String;Landroidx/compose/runtime/Composer;II)V
200+
public static final fun Marker-_2sGejQ (Lcom/mapbox/geojson/Point;JJLandroidx/compose/ui/graphics/Color;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;II)V
201201
}
202202

203203
public final class com/mapbox/maps/extension/compose/annotation/ViewAnnotationKt {

extension-compose/src/androidTest/java/com/mapbox/maps/extension/compose/annotation/ViewAnnotationTest.kt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,59 @@ public class ViewAnnotationTest {
138138
composeTestRule.onNodeWithTag(MAP_TEST_TAG).assertExists()
139139
}
140140

141+
@OptIn(MapboxExperimental::class)
142+
@Test
143+
public fun testMarkerClickGesture() {
144+
var clicked = false
145+
val onClickCallback = { clicked = true }
146+
var viewAnnotationCount = 0
147+
148+
composeTestRule.setContent {
149+
MapboxMap(
150+
Modifier
151+
.fillMaxSize()
152+
.testTag(MAP_TEST_TAG),
153+
mapViewportState = rememberMapViewportState {
154+
setCameraOptions {
155+
zoom(ZOOM)
156+
center(HELSINKI)
157+
}
158+
},
159+
mapState = rememberMapState(),
160+
) {
161+
// Create a marker with click functionality
162+
Marker(
163+
point = HELSINKI,
164+
text = VIEW_ANNOTATION_TEXT,
165+
onClick = onClickCallback
166+
)
167+
168+
// Use MapEffect to count annotations
169+
MapEffect(Unit) { mapView ->
170+
viewAnnotationCount = mapView.viewAnnotationManager.annotations.size
171+
}
172+
}
173+
}
174+
composeTestRule.waitForIdle()
175+
176+
// Wait for marker to be added
177+
composeTestRule.waitUntil(timeoutMillis = VIEW_APPEAR_TIMEOUT_MS, condition = {
178+
viewAnnotationCount > 0
179+
})
180+
181+
// Verify marker was created
182+
assert(viewAnnotationCount == 1) { "Expected 1 view annotation, got $viewAnnotationCount" }
183+
184+
// Verify callback is not yet triggered
185+
assert(!clicked) { "Click callback should not be triggered yet" }
186+
187+
// Execute the click action directly (similar to how iOS test calls marker.tapAction?())
188+
onClickCallback()
189+
190+
// Verify that our click callback was invoked
191+
assert(clicked) { "Expected click callback to be triggered" }
192+
}
193+
141194
private fun setMapContent(
142195
cameraCenter: Point,
143196
annotationCenter: Point,

extension-compose/src/main/java/com/mapbox/maps/extension/compose/annotation/Marker.kt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import android.text.TextPaint
1010
import android.text.TextUtils
1111
import androidx.compose.foundation.Canvas
1212
import androidx.compose.foundation.Image
13+
import androidx.compose.foundation.clickable
14+
import androidx.compose.foundation.interaction.MutableInteractionSource
1315
import androidx.compose.foundation.layout.Arrangement
1416
import androidx.compose.foundation.layout.Box
1517
import androidx.compose.foundation.layout.Column
@@ -60,6 +62,7 @@ import java.lang.ref.WeakReference
6062
* @param innerColor The inner color of the [Marker]
6163
* @param stroke The color of optional strokes. Pass null to remove the strokes.
6264
* @param text A text to be displayed with the [Marker]
65+
* @param onClick Optional callback to be invoked when the marker is clicked
6366
*/
6467
@Composable
6568
@MapboxMapComposable
@@ -69,7 +72,8 @@ public fun Marker(
6972
color: Color = Color(0xffcfdaf7),
7073
innerColor: Color = Color(0xffffffff),
7174
stroke: Color? = Color(0xff3a59fa),
72-
text: String? = null
75+
text: String? = null,
76+
onClick: (() -> Unit)? = null
7377
) {
7478
val stableOptions = remember(point) {
7579
viewAnnotationOptions {
@@ -87,7 +91,17 @@ public fun Marker(
8791
) {
8892
Column(
8993
modifier = Modifier
90-
.size(width = 120.dp, height = 100.dp),
94+
.size(width = 120.dp, height = 100.dp)
95+
.run {
96+
if (onClick != null) {
97+
clickable(
98+
indication = null,
99+
interactionSource = remember { MutableInteractionSource() }
100+
) { onClick() }
101+
} else {
102+
this
103+
}
104+
},
91105
horizontalAlignment = Alignment.CenterHorizontally,
92106
verticalArrangement = Arrangement.Top
93107
) {

0 commit comments

Comments
 (0)