diff --git a/maps-app/src/main/java/com/google/maps/android/compose/markerexamples/MarkerClusteringActivity.kt b/maps-app/src/main/java/com/google/maps/android/compose/markerexamples/MarkerClusteringActivity.kt index 86ba4a61..2054460d 100644 --- a/maps-app/src/main/java/com/google/maps/android/compose/markerexamples/MarkerClusteringActivity.kt +++ b/maps-app/src/main/java/com/google/maps/android/compose/markerexamples/MarkerClusteringActivity.kt @@ -32,6 +32,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalConfiguration @@ -184,6 +185,7 @@ private fun CustomUiClustering(items: List) { }, // Optional: Custom rendering for non-clustered items clusterItemContent = null, + clusterContentAnchor = Offset(0.5f, 0.5f), // Optional: Customization hook for clusterManager and renderer when they're ready onClusterManager = { clusterManager -> (clusterManager.renderer as DefaultClusterRenderer).minClusterSize = 2 diff --git a/maps-compose-utils/src/main/java/com/google/maps/android/compose/clustering/ClusterRenderer.kt b/maps-compose-utils/src/main/java/com/google/maps/android/compose/clustering/ClusterRenderer.kt index b96cf469..f4cb051d 100644 --- a/maps-compose-utils/src/main/java/com/google/maps/android/compose/clustering/ClusterRenderer.kt +++ b/maps-compose-utils/src/main/java/com/google/maps/android/compose/clustering/ClusterRenderer.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.platform.AbstractComposeView import androidx.core.graphics.applyCanvas import androidx.core.view.doOnAttach import androidx.core.view.doOnDetach +import androidx.compose.ui.geometry.Offset import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.BitmapDescriptor import com.google.android.gms.maps.model.BitmapDescriptorFactory @@ -41,6 +42,10 @@ internal class ComposeUiClusterRenderer( private val viewRendererState: State, private val clusterContentState: State<@Composable ((Cluster) -> Unit)?>, private val clusterItemContentState: State<@Composable ((T) -> Unit)?>, + private val clusterContentAnchorState: State, + private val clusterItemContentAnchorState: State, + private val clusterContentZIndexState: State, + private val clusterItemContentZIndexState: State, ) : DefaultClusterRenderer( context, map, @@ -144,6 +149,16 @@ internal class ComposeUiClusterRenderer( } + override fun onBeforeClusterRendered(cluster: Cluster, markerOptions: MarkerOptions) { + super.onBeforeClusterRendered(cluster, markerOptions) + + if (clusterContentState.value != null) { + val anchor = clusterContentAnchorState.value + markerOptions.anchor(anchor.x, anchor.y) + markerOptions.zIndex(clusterContentZIndexState.value) + } + } + override fun getDescriptorForCluster(cluster: Cluster): BitmapDescriptor { return if (clusterContentState.value != null) { val viewInfo = keysToViews.entries @@ -165,6 +180,10 @@ internal class ComposeUiClusterRenderer( ?.value ?: createAndAddView(ViewKey.Item(item)) markerOptions.icon(renderViewToBitmapDescriptor(viewInfo.view)) + + val anchor = clusterItemContentAnchorState.value + markerOptions.anchor(anchor.x, anchor.y) + markerOptions.zIndex(clusterItemContentZIndexState.value) } } diff --git a/maps-compose-utils/src/main/java/com/google/maps/android/compose/clustering/Clustering.kt b/maps-compose-utils/src/main/java/com/google/maps/android/compose/clustering/Clustering.kt index 01968f23..32c723d2 100644 --- a/maps-compose-utils/src/main/java/com/google/maps/android/compose/clustering/Clustering.kt +++ b/maps-compose-utils/src/main/java/com/google/maps/android/compose/clustering/Clustering.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.UiComposable +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalContext import com.google.android.gms.maps.GoogleMap import com.google.maps.android.clustering.Cluster @@ -42,6 +43,10 @@ import kotlinx.coroutines.launch * window of a non-clustered item * @param clusterContent an optional Composable that is rendered for each [Cluster]. * @param clusterItemContent an optional Composable that is rendered for each non-clustered item. + * @param clusterContentAnchor the anchor for the cluster image + * @param clusterItemContentAnchor the anchor for the non-clustered item image + * @param clusterContentZIndex the z-index of the cluster + * @param clusterItemContentZIndex the z-index of the non-clustered item * @param clusterRenderer an optional ClusterRenderer that can be used to specify the algorithm used by the rendering. */ @Composable @@ -85,10 +90,21 @@ public fun Clustering( onClusterItemInfoWindowLongClick: (T) -> Unit = { }, clusterContent: @[UiComposable Composable] ((Cluster) -> Unit)? = null, clusterItemContent: @[UiComposable Composable] ((T) -> Unit)? = null, + clusterContentAnchor: Offset = Offset(0.5f, 1.0f), + clusterItemContentAnchor: Offset = Offset(0.5f, 1.0f), + clusterContentZIndex: Float = 0.0f, + clusterItemContentZIndex: Float = 0.0f, clusterRenderer: ClusterRenderer? = null, ) { - val clusterManager = rememberClusterManager(clusterContent, clusterItemContent, clusterRenderer) - ?: return + val clusterManager = rememberClusterManager( + clusterContent, + clusterItemContent, + clusterContentAnchor, + clusterItemContentAnchor, + clusterContentZIndex, + clusterItemContentZIndex, + clusterRenderer + ) ?: return SideEffect { clusterManager.setOnClusterClickListener(onClusterClick) @@ -114,6 +130,10 @@ public fun Clustering( * window of a non-clustered item * @param clusterContent an optional Composable that is rendered for each [Cluster]. * @param clusterItemContent an optional Composable that is rendered for each non-clustered item. + * @param clusterContentAnchor the anchor for the cluster image + * @param clusterItemContentAnchor the anchor for the non-clustered item image + * @param clusterContentZIndex the z-index of the cluster + * @param clusterItemContentZIndex the z-index of the non-clustered item */ @Composable @GoogleMapComposable @@ -126,6 +146,10 @@ public fun Clustering( onClusterItemInfoWindowLongClick: (T) -> Unit = { }, clusterContent: @[UiComposable Composable] ((Cluster) -> Unit)? = null, clusterItemContent: @[UiComposable Composable] ((T) -> Unit)? = null, + clusterContentAnchor: Offset = Offset(0.5f, 1.0f), + clusterItemContentAnchor: Offset = Offset(0.5f, 1.0f), + clusterContentZIndex: Float = 0.0f, + clusterItemContentZIndex: Float = 0.0f, ) { Clustering( items = items, @@ -135,6 +159,10 @@ public fun Clustering( onClusterItemInfoWindowLongClick = onClusterItemInfoWindowLongClick, clusterContent = clusterContent, clusterItemContent = clusterItemContent, + clusterContentAnchor = clusterContentAnchor, + clusterItemContentAnchor = clusterItemContentAnchor, + clusterContentZIndex = clusterContentZIndex, + clusterItemContentZIndex = clusterItemContentZIndex, onClusterManager = null, ) } @@ -151,6 +179,10 @@ public fun Clustering( * window of a non-clustered item * @param clusterContent an optional Composable that is rendered for each [Cluster]. * @param clusterItemContent an optional Composable that is rendered for each non-clustered item. + * @param clusterContentAnchor the anchor for the cluster image + * @param clusterItemContentAnchor the anchor for the non-clustered item image + * @param clusterContentZIndex the z-index of the cluster + * @param clusterItemContentZIndex the z-index of the non-clustered item * @param onClusterManager an optional lambda invoked with the clusterManager as a param when both * the clusterManager and renderer are set up, allowing callers a customization hook. */ @@ -165,10 +197,22 @@ public fun Clustering( onClusterItemInfoWindowLongClick: (T) -> Unit = { }, clusterContent: @[UiComposable Composable] ((Cluster) -> Unit)? = null, clusterItemContent: @[UiComposable Composable] ((T) -> Unit)? = null, + clusterContentAnchor: Offset = Offset(0.5f, 1.0f), + clusterItemContentAnchor: Offset = Offset(0.5f, 1.0f), + clusterContentZIndex: Float = 0.0f, + clusterItemContentZIndex: Float = 0.0f, onClusterManager: ((ClusterManager) -> Unit)? = null, ) { val clusterManager = rememberClusterManager() - val renderer = rememberClusterRenderer(clusterContent, clusterItemContent, clusterManager) + val renderer = rememberClusterRenderer( + clusterContent, + clusterItemContent, + clusterContentAnchor, + clusterItemContentAnchor, + clusterContentZIndex, + clusterItemContentZIndex, + clusterManager + ) SideEffect { clusterManager ?: return@SideEffect @@ -266,6 +310,10 @@ public fun rememberClusterRenderer( * * @param clusterContent an optional Composable that is rendered for each [Cluster]. * @param clusterItemContent an optional Composable that is rendered for each non-clustered item. + * @param clusterContentAnchor the anchor for the cluster image + * @param clusterItemContentAnchor the anchor for the non-clustered item image + * @param clusterContentZIndex the z-index of the cluster + * @param clusterItemContentZIndex the z-index of the non-clustered item */ @Composable @GoogleMapComposable @@ -273,10 +321,18 @@ public fun rememberClusterRenderer( public fun rememberClusterRenderer( clusterContent: @Composable ((Cluster) -> Unit)?, clusterItemContent: @Composable ((T) -> Unit)?, + clusterContentAnchor: Offset = Offset(0.5f, 1.0f), + clusterItemContentAnchor: Offset = Offset(0.5f, 1.0f), + clusterContentZIndex: Float = 0.0f, + clusterItemContentZIndex: Float = 0.0f, clusterManager: ClusterManager?, ): ClusterRenderer? { val clusterContentState = rememberUpdatedState(clusterContent) val clusterItemContentState = rememberUpdatedState(clusterItemContent) + val clusterContentAnchorState = rememberUpdatedState(clusterContentAnchor) + val clusterItemContentAnchorState = rememberUpdatedState(clusterItemContentAnchor) + val clusterContentZIndexState = rememberUpdatedState(clusterContentZIndex) + val clusterItemContentZIndexState = rememberUpdatedState(clusterItemContentZIndex) val context = LocalContext.current val viewRendererState = rememberUpdatedState(rememberComposeUiViewRenderer()) val clusterRendererState: MutableState?> = remember { mutableStateOf(null) } @@ -291,6 +347,10 @@ public fun rememberClusterRenderer( viewRendererState, clusterContentState, clusterItemContentState, + clusterContentAnchorState, + clusterItemContentAnchorState, + clusterContentZIndexState, + clusterItemContentZIndexState, ) clusterRendererState.value = renderer awaitCancellation() @@ -315,10 +375,18 @@ public fun rememberClusterManager(): ClusterManager? { private fun rememberClusterManager( clusterContent: @Composable ((Cluster) -> Unit)?, clusterItemContent: @Composable ((T) -> Unit)?, + clusterContentAnchor: Offset = Offset(0.5f, 1.0f), + clusterItemContentAnchor: Offset = Offset(0.5f, 1.0f), + clusterContentZIndex: Float = 0.0f, + clusterItemContentZIndex: Float = 0.0f, clusterRenderer: ClusterRenderer? = null, ): ClusterManager? { val clusterContentState = rememberUpdatedState(clusterContent) val clusterItemContentState = rememberUpdatedState(clusterItemContent) + val clusterContentAnchorState = rememberUpdatedState(clusterContentAnchor) + val clusterItemContentAnchorState = rememberUpdatedState(clusterItemContentAnchor) + val clusterContentZIndexState = rememberUpdatedState(clusterContentZIndex) + val clusterItemContentZIndexState = rememberUpdatedState(clusterItemContentZIndex) val context = LocalContext.current val viewRendererState = rememberUpdatedState(rememberComposeUiViewRenderer()) val clusterManagerState: MutableState?> = remember { mutableStateOf(null) } @@ -332,7 +400,7 @@ private fun rememberClusterManager( .collect { hasCustomContent -> val renderer = clusterRenderer ?: if (hasCustomContent) { - ComposeUiClusterRenderer( + ComposeUiClusterRenderer( context, scope = this, map, @@ -340,6 +408,10 @@ private fun rememberClusterManager( viewRendererState, clusterContentState, clusterItemContentState, + clusterContentAnchorState, + clusterItemContentAnchorState, + clusterContentZIndexState, + clusterItemContentZIndexState, ) } else { DefaultClusterRenderer(context, map, clusterManager)