Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: customize Marker using Composable #355

Merged
merged 6 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,26 @@ GoogleMap(
}
```

You can also customize the marker you want to add by using `MarkerComposable`.

```kotlin
val state = MyState()

GoogleMap(
//...
) {
MarkerComposable(
keys = arrayOf(state),
state = MarkerState(position = LatLng(-34, 151)),
) {
MyCustomMarker(state)
}
}
```
As this Composable is backed by a rendering of your Composable into a Bitmap, it will not render
your Composable every recomposition. So to trigger a new render of your Composable, you can pass
all variables that your Composable depends on to trigger a render whenever one of them change.

</details>

<details>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.CircularProgressIndicator
Expand All @@ -46,9 +48,12 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.google.android.gms.maps.CameraUpdateFactory
Expand All @@ -64,6 +69,7 @@ private const val TAG = "BasicMapActivity"
val singapore = LatLng(1.3588227, 103.8742114)
val singapore2 = LatLng(1.40, 103.77)
val singapore3 = LatLng(1.45, 103.77)
val singapore4 = LatLng(1.50, 103.77)
val defaultCameraPosition = CameraPosition.fromLatLngZoom(singapore, 11f)

class BasicMapActivity : ComponentActivity() {
Expand Down Expand Up @@ -115,6 +121,7 @@ fun GoogleMapView(
val singaporeState = rememberMarkerState(position = singapore)
val singapore2State = rememberMarkerState(position = singapore2)
val singapore3State = rememberMarkerState(position = singapore3)
val singapore4State = rememberMarkerState(position = singapore4)
var circleCenter by remember { mutableStateOf(singapore) }
if (singaporeState.dragState == DragState.END) {
circleCenter = singaporeState.position
Expand Down Expand Up @@ -168,6 +175,25 @@ fun GoogleMapView(
title = "Marker in Singapore",
onClick = markerClick
)
Marker(
mosmb marked this conversation as resolved.
Show resolved Hide resolved
keys = arrayOf("singapore4"),
state = singapore4State,
onClick = markerClick,
) {
Box(
modifier = Modifier
.width(88.dp)
.height(36.dp)
.clip(RoundedCornerShape(16.dp))
.background(Color.Red),
contentAlignment = Alignment.Center,
) {
Text(
text = "Compose Marker",
textAlign = TextAlign.Center,
)
}
}
Circle(
center = circleCenter,
fillColor = MaterialTheme.colors.secondary,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public enum class DragState {
* @param position the initial marker position
*/
public class MarkerState(
position: LatLng = LatLng(0.0, 0.0)
position: LatLng = LatLng(0.0, 0.0),
) {
/**
* Current position of the marker.
Expand Down Expand Up @@ -190,6 +190,75 @@ public fun Marker(
)
}

/**
* Composable rendering the content passed as a marker.
*
* @param keys unique keys representing the state of this Marker. Any changes to one of the key will
* trigger a rendering of the content composable and thus the rendering of an updated marker.
* @param state the [MarkerState] to be used to control or observe the marker
* state such as its position and info window
* @param alpha the alpha (opacity) of the marker
* @param anchor the anchor for the marker image
* @param draggable sets the draggability for the marker
* @param flat sets if the marker should be flat against the map
* @param infoWindowAnchor the anchor point of the info window on the marker image
* @param rotation the rotation of the marker in degrees clockwise about the marker's anchor point
* @param snippet the snippet for the marker
* @param tag optional tag to associate with the marker
* @param title the title for the marker
* @param visible the visibility of the marker
* @param zIndex the z-index of the marker
* @param onClick a lambda invoked when the marker is clicked
* @param onInfoWindowClick a lambda invoked when the marker's info window is clicked
* @param onInfoWindowClose a lambda invoked when the marker's info window is closed
* @param onInfoWindowLongClick a lambda invoked when the marker's info window is long clicked
* @param content composable lambda expression used to customize the marker's content
*/
@Composable
@GoogleMapComposable
public fun MarkerComposable(
vararg keys: Any,
state: MarkerState = rememberMarkerState(),
alpha: Float = 1.0f,
anchor: Offset = Offset(0.5f, 1.0f),
draggable: Boolean = false,
flat: Boolean = false,
infoWindowAnchor: Offset = Offset(0.5f, 0.0f),
rotation: Float = 0.0f,
snippet: String? = null,
tag: Any? = null,
title: String? = null,
visible: Boolean = true,
zIndex: Float = 0.0f,
onClick: (Marker) -> Boolean = { false },
onInfoWindowClick: (Marker) -> Unit = {},
onInfoWindowClose: (Marker) -> Unit = {},
onInfoWindowLongClick: (Marker) -> Unit = {},
content: @Composable () -> Unit,
) {
val icon = rememberComposeBitmapDescriptor(*keys) { content() }

MarkerImpl(
state = state,
alpha = alpha,
anchor = anchor,
draggable = draggable,
flat = flat,
icon = icon,
infoWindowAnchor = infoWindowAnchor,
rotation = rotation,
snippet = snippet,
tag = tag,
title = title,
visible = visible,
zIndex = zIndex,
onClick = onClick,
onInfoWindowClick = onInfoWindowClick,
onInfoWindowClose = onInfoWindowClose,
onInfoWindowLongClick = onInfoWindowLongClick,
)
}

/**
* A composable for a marker on the map wherein its entire info window can be
* customized. If this customization is not required, use
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.google.maps.android.compose

import android.graphics.Bitmap
import android.graphics.Canvas
import android.view.View
import android.view.ViewGroup
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionContext
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCompositionContext
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalView
import androidx.core.graphics.applyCanvas
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.BitmapDescriptorFactory

@Composable
internal fun rememberComposeBitmapDescriptor(
vararg keys: Any,
content: @Composable () -> Unit,
): BitmapDescriptor {
val parent = LocalView.current as ViewGroup
val compositionContext = rememberCompositionContext()
val currentContent by rememberUpdatedState(content)

return remember(parent, compositionContext, currentContent, *keys) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are having a funny error here since the vararg is not properly resolved, but does not seem to affect the compilation and we can probably live with it.

renderComposableToBitmapDescriptor(parent, compositionContext, currentContent)
}
}

private fun renderComposableToBitmapDescriptor(
parent: ViewGroup,
compositionContext: CompositionContext,
content: @Composable () -> Unit,
): BitmapDescriptor {
val fakeCanvas = Canvas()
val composeView =
ComposeView(parent.context)
.apply {
setParentCompositionContext(compositionContext)
setContent(content)
}
.also(parent::addView)

composeView.draw(fakeCanvas)

composeView.measure(
View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.AT_MOST),
View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.AT_MOST),
)

composeView.layout(0, 0, composeView.measuredWidth, composeView.measuredHeight)

val bitmap =
Bitmap
.createBitmap(
composeView.measuredWidth,
composeView.measuredHeight,
Bitmap.Config.ARGB_8888,
)

bitmap.applyCanvas { composeView.draw(this) }

parent.removeView(composeView)

return BitmapDescriptorFactory.fromBitmap(bitmap)
}
Loading