Skip to content

Commit

Permalink
feat: Custom info window. (#22)
Browse files Browse the repository at this point in the history
* feat: Custom info window.

Change-Id: Id5cc2609f49faeb440a5eaa0c8f9ab0c69a6f55d

* Create ComposeInfoWindowAdapter.

Change-Id: Ic984680283e6a5f13e8187959769279fec5cef38

* Creating ComposeInfoWindowComponent

Change-Id: Ib3c4564c8f4d0e40dc51db5d45a1587508b2e84f

* Documentation.

Change-Id: If1101d49c2d77151a845f295741502af0f36a7fe

* Add instructions to READM.

Change-Id: I55753e939e7e62d1014c4494c37e98b5a969e737

* Using infoContents and infoWindow properties for customizing the info window.

Change-Id: Ib1a146a343b5ac4c9006b33226054f16c23299fb

* Rename to

Change-Id: I5a536a89c9fe0d1616a54943974b89857c1512d4

* Update README.md

Co-authored-by: Ben Trengrove <bentrengrove@users.noreply.github.com>

* Update README.md

* Update ComposeInfoWindowAdapter.kt

* Create MarkerInfoWindow and MarkerInfoWindowContent.

Change-Id: I3ebe7f2b7deb74278c0187a91ec062dd1cfc7d59

* Update documentation.

Change-Id: Ia985a45dc9990647a873682bca20b5e559e3cf26

* Fix build issues.

Change-Id: I199905fc53d02fb2d19022ba45867ee92b747efe

Co-authored-by: Ben Trengrove <bentrengrove@users.noreply.github.com>
  • Loading branch information
arriolac and bentrengrove authored Feb 14, 2022
1 parent 6fbc9fa commit 8ef8074
Show file tree
Hide file tree
Showing 7 changed files with 377 additions and 20 deletions.
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Box(Modifier.fillMaxSize()) {
}
```

### Controlling the map's camera
### Controlling a map's camera

Camera changes and updates can be observed and controlled via `CameraPositionState`.

Expand Down Expand Up @@ -95,6 +95,32 @@ GoogleMap(
}
```

#### Customizing a marker's info window

You can customize a marker's info window contents by using the
`MarkerInfoWindowContent` element, or if you want to customize the entire info
window, use the `MarkerInfoWindow` element instead. Both of these elements
accept a `content` parameter to provide your customization in a composable
lambda expression.

```kotlin
MarkerInfoWindowContent(
//...
) { marker ->
Text(marker.title ?: "Default Marker Title", color = Color.Red)
}

MarkerInfoWindow(
//...
) { marker ->
// Implement the custom info window here
Column {
Text(marker.title ?: "Default Marker Title", color = Color.Red)
Text(marker.snippet ?: "Default Marker Snippet", color = Color.Red)
}
}
```

## Sample App

This repository includes a [sample app](app).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,14 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMapOptions
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Marker
import kotlinx.coroutines.launch

private const val TAG = "MapSampleActivity"
Expand Down Expand Up @@ -77,7 +80,8 @@ class MapSampleActivity : ComponentActivity() {
exit = fadeOut()
) {
CircularProgressIndicator(
modifier = Modifier.background(MaterialTheme.colors.background)
modifier = Modifier
.background(MaterialTheme.colors.background)
.wrapContentSize()
)
}
Expand All @@ -90,6 +94,8 @@ class MapSampleActivity : ComponentActivity() {
@Composable
private fun GoogleMapView(modifier: Modifier, onMapLoaded: () -> Unit) {
val singapore = LatLng(1.35, 103.87)
val singapore2 = LatLng(1.40, 103.77)

// Observing and controlling the camera's state can be done with a CameraPositionState
val cameraPositionState = rememberCameraPositionState {
position = CameraPosition.fromLatLngZoom(singapore, 11f)
Expand All @@ -116,14 +122,25 @@ private fun GoogleMapView(modifier: Modifier, onMapLoaded: () -> Unit) {
}
) {
// Drawing on the map is accomplished with a child-based API
Marker(
val markerClick: (Marker) -> Boolean = {
Log.d(TAG, "${it.title} was clicked")
false
}
MarkerInfoWindowContent(
position = singapore,
title = "Zoom in has been tapped $ticker times.",
onClick = {
println("${it.title} was clicked")
false
}
)
onClick = markerClick,
) {
Text(it.title ?: "Title", color = Color.Red)
}
MarkerInfoWindowContent(
position = singapore2,
title = "Marker with custom info window.\nZoom in has been tapped $ticker times.",
icon = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE),
onClick = markerClick,
) {
Text(it.title ?: "Title", color = Color.Blue)
}
Circle(
center = singapore,
fillColor = MaterialTheme.colors.secondary,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.google.maps.android.compose

import android.view.View
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionContext
import androidx.compose.ui.platform.ComposeView
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.MapView
import com.google.android.gms.maps.model.Marker

/**
* An InfoWindowAdapter that returns a [ComposeView] for drawing a marker's
* info window.
*
* Note: As of version 18.0.2 of the Maps SDK, info windows are drawn by
* creating a bitmap of the [View]s returned in the [GoogleMap.InfoWindowAdapter]
* interface methods. The returned views are never attached to a window,
* instead, they are drawn to a bitmap canvas. This breaks the assumption
* [ComposeView] makes where it must eventually be attached to a window. As a
* workaround, the contained window is temporarily attached to the MapView so
* that the contents of the ComposeViews are rendered.
*
* Eventually when info windows are no longer implemented this way, this
* implementation should be updated.
*/
internal class ComposeInfoWindowAdapter(
private val mapView: MapView,
private val markerNodeFinder: (Marker) -> MarkerNode?
) : GoogleMap.InfoWindowAdapter {

private val infoWindowView: ComposeView
get() = ComposeView(mapView.context).apply {
mapView.addView(this)
}

override fun getInfoContents(marker: Marker): View? {
val markerNode = markerNodeFinder(marker) ?: return null
val content = markerNode.infoContent
if (content == null) {
return null
}
return infoWindowView.applyAndRemove(markerNode.compositionContext) {
content(marker)
}
}

override fun getInfoWindow(marker: Marker): View? {
val markerNode = markerNodeFinder(marker) ?: return null
val infoWindow = markerNode.infoWindow
if (infoWindow == null) {
return null
}
return infoWindowView.applyAndRemove(markerNode.compositionContext) {
infoWindow(marker)
}
}

private fun ComposeView.applyAndRemove(
parentContext: CompositionContext,
content: @Composable () -> Unit
): ComposeView {
val result = this.apply {
setParentCompositionContext(parentContext)
setContent(content)
}
(this.parent as? MapView)?.removeView(this)
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@
package com.google.maps.android.compose

import android.content.ComponentCallbacks
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
Expand Down Expand Up @@ -109,9 +113,8 @@ fun GoogleMap(
val currentContent by rememberUpdatedState(content)

LaunchedEffect(Unit) {
val map = mapView.awaitMap()
disposingComposition {
map.newComposition(parentComposition) {
mapView.newComposition(parentComposition) {
MapUpdater(
contentDescription = contentDescription,
cameraPositionState = currentCameraPositionState,
Expand All @@ -137,11 +140,16 @@ private suspend inline fun disposingComposition(factory: () -> Composition) {
}
}

private fun GoogleMap.newComposition(
private suspend inline fun MapView.newComposition(
parent: CompositionContext,
content: @Composable () -> Unit
): Composition = Composition(MapApplier(this), parent).apply {
setContent(content)
noinline content: @Composable () -> Unit
): Composition {
val map = awaitMap()
return Composition(
MapApplier(map, this), parent
).apply {
setContent(content)
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
package com.google.maps.android.compose

import androidx.compose.runtime.AbstractApplier
import androidx.compose.ui.platform.ComposeView
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.MapView
import com.google.android.gms.maps.model.Circle
import com.google.android.gms.maps.model.GroundOverlay
import com.google.android.gms.maps.model.Marker
Expand All @@ -30,7 +32,8 @@ internal interface MapNode {
private object MapNodeRoot : MapNode

internal class MapApplier(
val map: GoogleMap
val map: GoogleMap,
private val mapView: MapView,
) : AbstractApplier<MapNode>(MapNodeRoot) {

private val decorations = mutableListOf<MapNode>()
Expand Down Expand Up @@ -109,20 +112,29 @@ internal class MapApplier(
}
map.setOnMarkerDragListener(object : GoogleMap.OnMarkerDragListener {
override fun onMarkerDrag(marker: Marker) {
val markerDragState = decorations.nodeForMarker(marker)?.markerDragState
val markerDragState =
decorations.nodeForMarker(marker)?.markerDragState
markerDragState?.dragState = DragState.DRAG
}

override fun onMarkerDragEnd(marker: Marker) {
val markerDragState = decorations.nodeForMarker(marker)?.markerDragState
val markerDragState =
decorations.nodeForMarker(marker)?.markerDragState
markerDragState?.dragState = DragState.END
}

override fun onMarkerDragStart(marker: Marker) {
val markerDragState = decorations.nodeForMarker(marker)?.markerDragState
val markerDragState =
decorations.nodeForMarker(marker)?.markerDragState
markerDragState?.dragState = DragState.START
}
})
map.setInfoWindowAdapter(
ComposeInfoWindowAdapter(
mapView,
markerNodeFinder = { decorations.nodeForMarker(it) }
)
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,4 @@ internal inline fun MapUpdater(
update(cameraPositionState) { this.cameraPositionState = it }
update(clickListeners) { this.clickListeners = it }
}
}
}
Loading

0 comments on commit 8ef8074

Please sign in to comment.