Skip to content

Commit f54d4ea

Browse files
kikosodkhawk
andauthored
fix: GroundOverlay (#826)
Co-authored-by: Dale Hawkins <107309+dkhawk@users.noreply.github.com>
1 parent 49758df commit f54d4ea

File tree

8 files changed

+218
-27
lines changed

8 files changed

+218
-27
lines changed

maps-app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@
106106
<activity
107107
android:name=".TileOverlayActivity"
108108
android:exported="true" />
109+
<activity
110+
android:name=".GroundOverlayActivity"
111+
android:exported="true" />
109112

110113
<!-- Used by createComponentActivity() for unit testing -->
111114
<activity android:name="androidx.activity.ComponentActivity" />

maps-app/src/main/java/com/google/maps/android/compose/BasicMapActivity.kt

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ import androidx.compose.foundation.layout.systemBarsPadding
3838
import androidx.compose.foundation.layout.width
3939
import androidx.compose.foundation.layout.wrapContentSize
4040
import androidx.compose.foundation.shape.RoundedCornerShape
41-
import androidx.compose.material.Button
42-
import androidx.compose.material.ButtonDefaults
43-
import androidx.compose.material.CircularProgressIndicator
44-
import androidx.compose.material.MaterialTheme
45-
import androidx.compose.material.Switch
46-
import androidx.compose.material.Text
41+
import androidx.compose.material3.Button
42+
import androidx.compose.material3.ButtonDefaults
43+
import androidx.compose.material3.CircularProgressIndicator
44+
import androidx.compose.material3.MaterialTheme
45+
import androidx.compose.material3.Switch
46+
import androidx.compose.material3.Text
4747
import androidx.compose.runtime.Composable
4848
import androidx.compose.runtime.getValue
4949
import androidx.compose.runtime.mutableIntStateOf
@@ -123,7 +123,7 @@ class BasicMapActivity : ComponentActivity() {
123123
) {
124124
CircularProgressIndicator(
125125
modifier = Modifier
126-
.background(MaterialTheme.colors.background)
126+
.background(MaterialTheme.colorScheme.background)
127127
.wrapContentSize()
128128
)
129129
}
@@ -255,8 +255,8 @@ fun GoogleMapView(
255255

256256
Circle(
257257
center = circleCenter,
258-
fillColor = MaterialTheme.colors.secondary,
259-
strokeColor = MaterialTheme.colors.secondaryVariant,
258+
fillColor = MaterialTheme.colorScheme.secondary,
259+
strokeColor = MaterialTheme.colorScheme.secondary,
260260
radius = 1000.0,
261261
)
262262

@@ -400,12 +400,12 @@ private fun MapButton(text: String, onClick: () -> Unit, modifier: Modifier = Mo
400400
Button(
401401
modifier = modifier.padding(4.dp),
402402
colors = ButtonDefaults.buttonColors(
403-
backgroundColor = MaterialTheme.colors.onPrimary,
404-
contentColor = MaterialTheme.colors.primary
403+
containerColor = MaterialTheme.colorScheme.onPrimary,
404+
contentColor = MaterialTheme.colorScheme.primary
405405
),
406406
onClick = onClick
407407
) {
408-
Text(text = text, style = MaterialTheme.typography.body1)
408+
Text(text = text, style = MaterialTheme.typography.bodyLarge)
409409
}
410410
}
411411

maps-app/src/main/java/com/google/maps/android/compose/Demo.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ sealed class ActivityGroup(
111111
R.string.tile_overlay_activity_description,
112112
TileOverlayActivity::class
113113
),
114+
Activity(
115+
R.string.ground_overlay_activity,
116+
R.string.ground_overlay_activity_description,
117+
GroundOverlayActivity::class
118+
),
114119
)
115120
)
116121

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.maps.android.compose
16+
17+
import android.os.Bundle
18+
import java.util.Locale
19+
import androidx.activity.ComponentActivity
20+
import androidx.activity.compose.setContent
21+
import androidx.activity.enableEdgeToEdge
22+
import androidx.compose.foundation.layout.Box
23+
import androidx.compose.foundation.layout.Column
24+
import androidx.compose.foundation.layout.Row
25+
import androidx.compose.foundation.layout.fillMaxSize
26+
import androidx.compose.foundation.layout.fillMaxWidth
27+
import androidx.compose.foundation.layout.padding
28+
import androidx.compose.foundation.layout.systemBarsPadding
29+
import androidx.compose.material3.MaterialTheme
30+
import androidx.compose.material3.Slider
31+
import androidx.compose.material3.Surface
32+
import androidx.compose.material3.Switch
33+
import androidx.compose.material3.Text
34+
import androidx.compose.runtime.Composable
35+
import androidx.compose.runtime.getValue
36+
import androidx.compose.runtime.mutableFloatStateOf
37+
import androidx.compose.runtime.mutableStateOf
38+
import androidx.compose.runtime.remember
39+
import androidx.compose.runtime.setValue
40+
import androidx.compose.ui.Alignment
41+
import androidx.compose.ui.Modifier
42+
import androidx.compose.ui.unit.dp
43+
import com.google.android.gms.maps.model.BitmapDescriptorFactory
44+
import com.google.android.gms.maps.model.LatLng
45+
import com.google.android.gms.maps.model.LatLngBounds
46+
import com.google.maps.android.compose.theme.MapsComposeSampleTheme
47+
import androidx.core.graphics.createBitmap
48+
49+
class GroundOverlayActivity : ComponentActivity() {
50+
51+
override fun onCreate(savedInstanceState: Bundle?) {
52+
super.onCreate(savedInstanceState)
53+
enableEdgeToEdge()
54+
setContent {
55+
MapsComposeSampleTheme {
56+
GroundOverlayScreen()
57+
}
58+
}
59+
}
60+
}
61+
62+
@Composable
63+
fun GroundOverlayScreen() {
64+
val singapore = LatLng(1.3588227, 103.8742114)
65+
val cameraPositionState = rememberCameraPositionState {
66+
position = com.google.android.gms.maps.model.CameraPosition.fromLatLngZoom(singapore, 12f)
67+
}
68+
69+
var isVisible by remember { mutableStateOf(true) }
70+
var transparency by remember { mutableFloatStateOf(0f) }
71+
var bearing by remember { mutableFloatStateOf(0f) }
72+
73+
val context = androidx.compose.ui.platform.LocalContext.current
74+
val imageDescriptor = remember {
75+
val drawable = androidx.core.content.ContextCompat.getDrawable(context, R.mipmap.ic_launcher)
76+
val bitmap = createBitmap(drawable!!.intrinsicWidth, drawable.intrinsicHeight)
77+
val canvas = android.graphics.Canvas(bitmap)
78+
drawable.setBounds(0, 0, canvas.width, canvas.height)
79+
drawable.draw(canvas)
80+
BitmapDescriptorFactory.fromBitmap(bitmap)
81+
}
82+
83+
Box(
84+
modifier = Modifier
85+
.fillMaxSize()
86+
.systemBarsPadding()
87+
) {
88+
GoogleMap(
89+
modifier = Modifier.fillMaxSize(),
90+
cameraPositionState = cameraPositionState
91+
) {
92+
// Stable GroundOverlay (using remembered state)
93+
GroundOverlay(
94+
position = GroundOverlayPosition.create(
95+
LatLngBounds(
96+
LatLng(1.35, 103.86),
97+
LatLng(1.37, 103.88)
98+
)
99+
),
100+
image = imageDescriptor,
101+
visible = isVisible,
102+
transparency = transparency,
103+
bearing = bearing
104+
)
105+
106+
// Stress-test GroundOverlay: Re-creating position every recomposition
107+
// This would cause a crash/rendering loop if GroundOverlayPosition was not a data class
108+
GroundOverlay(
109+
position = GroundOverlayPosition.create(
110+
LatLngBounds(
111+
LatLng(1.36, 103.89),
112+
LatLng(1.38, 103.91)
113+
)
114+
),
115+
image = imageDescriptor,
116+
transparency = 0.5f,
117+
zIndex = 1f
118+
)
119+
}
120+
121+
Column(
122+
modifier = Modifier
123+
.align(Alignment.BottomCenter)
124+
.fillMaxWidth()
125+
.padding(16.dp)
126+
) {
127+
GroundOverlayControls(
128+
isVisible = isVisible,
129+
onVisibilityChange = { isVisible = it },
130+
transparency = transparency,
131+
onTransparencyChange = { transparency = it },
132+
bearing = bearing,
133+
onBearingChange = { bearing = it }
134+
)
135+
}
136+
}
137+
}
138+
139+
@Composable
140+
fun GroundOverlayControls(
141+
isVisible: Boolean,
142+
onVisibilityChange: (Boolean) -> Unit,
143+
transparency: Float,
144+
onTransparencyChange: (Float) -> Unit,
145+
bearing: Float,
146+
onBearingChange: (Float) -> Unit
147+
) {
148+
Surface(
149+
shape = MaterialTheme.shapes.medium,
150+
tonalElevation = 4.dp,
151+
color = MaterialTheme.colorScheme.surface.copy(alpha = 0.8f),
152+
modifier = Modifier.fillMaxWidth()
153+
) {
154+
Column(modifier = Modifier.padding(16.dp)) {
155+
Row(verticalAlignment = Alignment.CenterVertically) {
156+
Text(text = "Visible", modifier = Modifier.weight(1f))
157+
Switch(checked = isVisible, onCheckedChange = onVisibilityChange)
158+
}
159+
Text(text = "Transparency: ${String.format(Locale.getDefault(), "%.2f", transparency)}")
160+
Slider(
161+
value = transparency,
162+
onValueChange = onTransparencyChange,
163+
valueRange = 0f..1f
164+
)
165+
Text(text = "Bearing: ${bearing.toInt()}°")
166+
Slider(
167+
value = bearing,
168+
onValueChange = onBearingChange,
169+
valueRange = 0f..360f
170+
)
171+
}
172+
}
173+
}

maps-app/src/main/java/com/google/maps/android/compose/markerexamples/MarkerClusteringActivity.kt

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.google.maps.android.compose.markerexamples
22

33
import android.os.Bundle
44
import android.util.Log
5+
import java.util.Locale
56
import androidx.activity.ComponentActivity
67
import androidx.activity.compose.setContent
78
import androidx.activity.enableEdgeToEdge
@@ -17,11 +18,11 @@ import androidx.compose.foundation.layout.padding
1718
import androidx.compose.foundation.layout.size
1819
import androidx.compose.foundation.layout.systemBarsPadding
1920
import androidx.compose.foundation.shape.CircleShape
20-
import androidx.compose.material.Button
21-
import androidx.compose.material.ButtonDefaults
22-
import androidx.compose.material.MaterialTheme
23-
import androidx.compose.material.Surface
24-
import androidx.compose.material.Text
21+
import androidx.compose.material3.Button
22+
import androidx.compose.material3.ButtonDefaults
23+
import androidx.compose.material3.MaterialTheme
24+
import androidx.compose.material3.Surface
25+
import androidx.compose.material3.Text
2526
import androidx.compose.runtime.Composable
2627
import androidx.compose.runtime.LaunchedEffect
2728
import androidx.compose.runtime.SideEffect
@@ -177,7 +178,7 @@ private fun CustomUiClustering(items: List<MyItem>) {
177178
clusterContent = { cluster ->
178179
CircleContent(
179180
modifier = Modifier.size(40.dp),
180-
text = "%,d".format(cluster.size),
181+
text = "%,d".format(Locale.getDefault(), cluster.size),
181182
color = Color.Blue,
182183
)
183184
},
@@ -210,7 +211,7 @@ fun CustomRendererClustering(items: List<MyItem>) {
210211
clusterContent = { cluster ->
211212
CircleContent(
212213
modifier = Modifier.size(40.dp),
213-
text = "%,d".format(cluster.size),
214+
text = "%,d".format(Locale.getDefault(), cluster.size),
214215
color = Color.Green,
215216
)
216217
},
@@ -306,12 +307,12 @@ private fun MapButton(text: String, onClick: () -> Unit, modifier: Modifier = Mo
306307
Button(
307308
modifier = modifier.padding(4.dp),
308309
colors = ButtonDefaults.buttonColors(
309-
backgroundColor = MaterialTheme.colors.onPrimary,
310-
contentColor = MaterialTheme.colors.primary
310+
containerColor = MaterialTheme.colorScheme.onPrimary,
311+
contentColor = MaterialTheme.colorScheme.primary
311312
),
312313
onClick = onClick
313314
) {
314-
Text(text = text, style = MaterialTheme.typography.body1)
315+
Text(text = text, style = MaterialTheme.typography.bodyLarge)
315316
}
316317
}
317318

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package com.google.maps.android.compose.theme
22

33
import androidx.compose.foundation.isSystemInDarkTheme
4-
import androidx.compose.material.MaterialTheme
5-
import androidx.compose.material.darkColors
6-
import androidx.compose.material.lightColors
4+
import androidx.compose.material3.MaterialTheme
5+
import androidx.compose.material3.darkColorScheme
6+
import androidx.compose.material3.lightColorScheme
77
import androidx.compose.runtime.Composable
88

99
@Composable
@@ -12,7 +12,7 @@ fun MapsComposeSampleTheme(
1212
content: @Composable () -> Unit
1313
) {
1414
MaterialTheme(
15-
colors = if (darkTheme) darkColors() else lightColors(),
15+
colorScheme = if (darkTheme) darkColorScheme() else lightColorScheme(),
1616
content = content
1717
)
1818
}

maps-app/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@
7272
<string name="tile_overlay_activity">Tile Overlay</string>
7373
<string name="tile_overlay_activity_description">Adding a tile overlay to the map.</string>
7474

75+
<string name="ground_overlay_activity">Ground Overlay</string>
76+
<string name="ground_overlay_activity_description">Adding a ground overlay to the map.</string>
77+
7578
<!-- Demo group titles -->
7679
<string name="map_types_title">Map Types</string>
7780
<string name="map_features_title">Map Features</string>

maps-compose/src/main/java/com/google/maps/android/compose/GroundOverlay.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ internal class GroundOverlayNode(
4040
*
4141
* Use one of the [create] methods to construct an instance of this class.
4242
*/
43-
public class GroundOverlayPosition private constructor(
43+
@ConsistentCopyVisibility
44+
public data class GroundOverlayPosition internal constructor(
4445
public val latLngBounds: LatLngBounds? = null,
4546
public val location: LatLng? = null,
4647
public val width: Float? = null,
@@ -116,6 +117,11 @@ public fun GroundOverlay(
116117
update(transparency) { this.groundOverlay.transparency = it }
117118
update(visible) { this.groundOverlay.isVisible = it }
118119
update(zIndex) { this.groundOverlay.zIndex = it }
120+
update(anchor) {
121+
// GroundOverlay does not have a setAnchor method.
122+
// We could recreate the overlay here, but that might be expensive.
123+
// For now, we'll document that anchor cannot be changed.
124+
}
119125
}
120126
)
121127
}

0 commit comments

Comments
 (0)