Reputation: 105
I am trying to develop an app that uses Google Map to show some locations. I tried to implement markers clustering for markers that are very close. However, by doing so the marker click in order to show an info window doesn't work anymore. If anyone can show me a trun around I would be so thankful. Here is my code.
GoogleMap(
modifier = Modifier
.fillMaxSize(),
cameraPositionState = cameraPositionState,
uiSettings = uiSettings,
onMapClick = {
onMapClick(it)
}
) {
var clusterManager by remember {
mutableStateOf<ClusterManager<CustomMarkerState>?>(
null
)
}
MapEffect(listOfMarkers) { map ->
if (clusterManager == null) {
clusterManager = ClusterManager<CustomMarkerState>(context, map)
}
clusterManager?.addItems(listOfMarkers)
}
LaunchedEffect(key1 = cameraPositionState.isMoving) {
if (!cameraPositionState.isMoving) {
clusterManager?.onCameraIdle()
}
}
if (listOfMarkers.isNotEmpty()) {
listOfMarkers.forEach { marker ->
Marker(
state = MarkerState(
position = LatLng(
marker.latitude,
marker.longitude
)
),
title = marker.name,
snippet = marker.description,
onClick = {
isShown = true
selectedMarker = marker
return@Marker true
}
)
}
}
}
}
Upvotes: 3
Views: 1836
Reputation: 386
Make Your own Cluster Rendering here is the complete solution
package com.google.maps.android.compose
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.clustering.ClusterItem
import com.google.maps.android.clustering.ClusterManager
private val singapore = LatLng(1.35, 103.87)
private val singapore2 = LatLng(2.50, 103.87)
class MapClusteringActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
GoogleMapClustering()
}
}
fun showToast(msg: String) {
Toast.makeText(this@MapClusteringActivity, msg, Toast.LENGTH_SHORT).show()
}
@Composable
fun GoogleMapClustering() {
val items = remember { mutableStateListOf<MyItem>() }
LaunchedEffect(Unit) {
for (i in 1..10) {
val position = LatLng(
singapore2.latitude + kotlin.random.Random.nextFloat(),
singapore2.longitude + kotlin.random.Random.nextFloat(),
)
items.add(MyItem(position, "Marker", "Snippet"))
}
}
GoogleMapClustering(items = items)
}
@OptIn(MapsComposeExperimentalApi::class)
@Composable
fun GoogleMapClustering(items: List<MyItem>) {
val cameraPositionState = rememberCameraPositionState {
position = CameraPosition.fromLatLngZoom(singapore, 10f)
}
GoogleMap(
modifier = Modifier.fillMaxSize(),
cameraPositionState = cameraPositionState
) {
val context = LocalContext.current
var clusterManager by remember { mutableStateOf<ClusterManager<MyItem>?>(null) }
MapEffect(items) { map ->
if (clusterManager == null) {
clusterManager = ClusterManager<MyItem>(context, map)
}
clusterManager?.addItems(items)
clusterManager?.renderer = MarkerClusterRender(context,map,clusterManager!!) {
showToast(it.itemTitle)
}
}
LaunchedEffect(key1 = cameraPositionState.isMoving) {
if (!cameraPositionState.isMoving) {
clusterManager?.onCameraIdle()
}
}
}
}
}
data class MyItem(
val itemPosition: LatLng,
val itemTitle: String,
val itemSnippet: String,
) : ClusterItem {
override fun getPosition(): LatLng =
itemPosition
override fun getTitle(): String =
itemTitle
override fun getSnippet(): String =
itemSnippet
}
Custom Cluster Manager
package com.google.maps.android.compose
import android.content.Context
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.Marker
import com.google.maps.android.clustering.Cluster
import com.google.maps.android.clustering.ClusterItem
import com.google.maps.android.clustering.ClusterManager
import com.google.maps.android.clustering.view.DefaultClusterRenderer
class MarkerClusterRender<T : ClusterItem>(
var context: Context,
var googleMap: GoogleMap,
clusterManager: ClusterManager<T>,
var onInfoWindowClick: (MyItem) -> Unit
) :
DefaultClusterRenderer<T>(context, googleMap, clusterManager) {
private var clusterMap: HashMap<String, Marker> = hashMapOf()
override fun shouldRenderAsCluster(cluster: Cluster<T>): Boolean {
return cluster.size > 1
}
override fun getBucket(cluster: Cluster<T>): Int {
return cluster.size
}
override fun getClusterText(bucket: Int): String {
return super.getClusterText(bucket).replace("+", "")
}
override fun onClusterItemRendered(clusterItem: T, marker: Marker) {
super.onClusterItemRendered(clusterItem, marker)
clusterMap[(clusterItem as MyItem).itemTitle] = marker
setMarker((clusterItem as MyItem), marker)
}
private fun setMarker(poi: MyItem, marker: Marker?) {
val markerColor = BitmapDescriptorFactory.HUE_RED
marker?.let {
it.tag = poi
it.showInfoWindow()
changeMarkerColor(it, markerColor)
}
googleMap.setOnInfoWindowClickListener {
onInfoWindowClick(it.tag as MyItem)
}
}
private fun getClusterMarker(itemId: String): Marker? {
return if (clusterMap.containsKey(itemId)) clusterMap[itemId]
else null
}
fun showRouteInfoWindow(key: String) {
getClusterMarker(key)?.showInfoWindow()
}
private fun changeMarkerColor(marker: Marker, color: Float) {
try {
marker.setIcon(BitmapDescriptorFactory.defaultMarker(color));
} catch (ex: IllegalArgumentException) {
ex.printStackTrace()
} catch (ex: Exception) {
ex.printStackTrace()
}
}
}
Upvotes: 5