1. Vorbereitung
In diesem Codelab erfahren Sie, wie Sie das Maps SDK for Android in Ihre App einbinden und die wichtigsten Funktionen nutzen. Dazu erstellen Sie eine App, in der eine Karte von Bergen in Colorado, USA, mit verschiedenen Arten von Markierungen angezeigt wird. Außerdem erfahren Sie, wie Sie andere Formen auf der Karte zeichnen.
So sieht es aus, wenn Sie das Codelab abgeschlossen haben:
Vorbereitung
- Grundkenntnisse in Kotlin, Jetpack Compose und Android-Entwicklung
Aufgaben
- Maps Compose-Bibliothek für das Maps SDK for Android aktivieren und verwenden, um einer Android-App eine
GoogleMap
hinzuzufügen - Markierungen hinzufügen und anpassen
- Polygone auf der Karte zeichnen
- Kamerablickwinkel programmatisch steuern
Voraussetzungen
- Maps SDK for Android
- Ein Google-Konto mit aktivierter Abrechnung
- Neueste stabile Version von Android Studio
- Ein Android-Gerät oder ein Android-Emulator, auf dem die Google APIs-Plattform auf Basis von Android 5.0 oder höher ausgeführt wird (eine Installationsanleitung finden Sie unter Apps im Android-Emulator ausführen).
- Eine Internetverbindung
2. Einrichten
Im nächsten Schritt müssen Sie das Maps SDK for Android aktivieren.
Google Maps Platform einrichten
Wenn Sie noch kein Google Cloud-Konto und kein Projekt mit aktivierter Abrechnung haben, lesen Sie bitte den Leitfaden Erste Schritte mit Google Maps Platform, um ein Rechnungskonto und ein Projekt zu erstellen.
- Klicken Sie in der Cloud Console auf das Drop-down-Menü für das Projekt und wählen Sie das Projekt aus, das Sie für dieses Codelab verwenden möchten.
- Aktivieren Sie die für dieses Codelab erforderlichen APIs und SDKs der Google Maps Platform im Google Cloud Marketplace. Folgen Sie dazu der Anleitung in diesem Video oder dieser Dokumentation.
- Generieren Sie einen API-Schlüssel in der Cloud Console auf der Seite Anmeldedaten. Folgen Sie dazu dieser Anleitung oder dieser Dokumentation. Für alle Anfragen an die Google Maps Platform ist ein API-Schlüssel erforderlich.
3. Schnelleinstieg
Damit Sie so schnell wie möglich loslegen können, finden Sie hier einige Startcodes, die Ihnen helfen, diesem Codelab zu folgen. Sie können direkt zur Lösung springen. Wenn Sie jedoch alle Schritte nachvollziehen möchten, um die Lösung selbst zu erstellen, lesen Sie weiter.
- Klonen Sie das Repository, wenn Sie
git
installiert haben.
git clone https://github.com/googlemaps-samples/codelab-maps-platform-101-compose.git
Alternativ können Sie auf die folgende Schaltfläche klicken, um den Quellcode herunterzuladen.
- Nachdem Sie den Code erhalten haben, öffnen Sie das Projekt im Verzeichnis
starter
in Android Studio.
4. API-Schlüssel zum Projekt hinzufügen
In diesem Abschnitt wird beschrieben, wie Sie Ihren API-Schlüssel speichern, damit er von Ihrer App sicherer referenziert werden kann. Er sollte nicht in Ihrem Versionsverwaltungssystem eingecheckt werden. Stattdessen empfehlen wir Ihnen, ihn in der Datei secrets.properties
zu speichern, die in Ihrer lokalen Kopie des Stammverzeichnisses Ihres Projekts abgelegt wird. Weitere Informationen zur Datei secrets.properties
finden Sie unter Gradle-Attributdateien.
Sie können das Secrets Gradle-Plug-in für Android verwenden, um diese Aufgabe zu optimieren.
So installieren Sie das Secrets Gradle-Plug-in für Android in Ihrem Google Maps-Projekt:
- Öffnen Sie in Android Studio die Datei
build.gradle.kts
auf oberster Ebene und fügen Sie den folgenden Code in dasdependencies
-Element unterbuildscript
ein.buildscript { dependencies { classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1") } }
- Öffnen Sie die Datei
build.gradle.kts
auf Modulebene und fügen Sie demplugins
-Element den folgenden Code hinzu.plugins { // ... id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") }
- In der Datei
build.gradle.kts
auf Modulebene müssentargetSdk
undcompileSdk
auf mindestens 34 gesetzt sein. - Speichern Sie die Datei und synchronisieren Sie Ihr Projekt mit Gradle.
- Öffnen Sie die Datei
secrets.properties
im Verzeichnis der obersten Ebene und fügen Sie den folgenden Code ein. Ersetzen Sie dabeiYOUR_API_KEY
durch Ihren eigenen API-Schlüssel. Speichern Sie den Schlüssel in dieser Datei, dasecrets.properties
nicht in ein Versionsverwaltungssystem eingecheckt werden kann.MAPS_API_KEY=YOUR_API_KEY
- Speichern Sie die Datei.
- Erstellen Sie die Datei
local.defaults.properties
im Verzeichnis der obersten Ebene, im selben Ordner wie die Dateisecrets.properties
, und fügen Sie folgenden Code ein. Diese Datei ist ein Ersatzspeicherort für den API-Schlüssel, damit Builds nicht fehlschlagen, falls die DateiMAPS_API_KEY=DEFAULT_API_KEY
secrets.properties
nicht gefunden wird. Das kann passieren, wenn Sie die App aus einem Versionsverwaltungssystem klonen und noch keine lokale Dateisecrets.properties
erstellt haben, um Ihren API-Schlüssel bereitzustellen. - Speichern Sie die Datei.
- Gehen Sie in der Datei
AndroidManifest.xml
zucom.google.android.geo.API_KEY
und aktualisieren Sie das Attributandroid:value
. Falls das<meta-data>
-Tag nicht vorhanden ist, erstellen Sie es als untergeordnetes Element des<application>
-Tags.<meta-data android:name="com.google.android.geo.API_KEY" android:value="${MAPS_API_KEY}" />
- Öffnen Sie in Android Studio die Datei
build.gradle.kts
auf Modulebene und bearbeiten Sie das Attributsecrets
. Wenn die Propertysecrets
nicht vorhanden ist, fügen Sie sie hinzu.Bearbeiten Sie die Eigenschaften des Plug-ins, umpropertiesFileName
aufsecrets.properties
,defaultPropertiesFileName
auflocal.defaults.properties
und alle anderen Eigenschaften festzulegen.secrets { // Optionally specify a different file name containing your secrets. // The plugin defaults to "local.properties" propertiesFileName = "secrets.properties" // A properties file containing default secret values. This file can be // checked in version control. defaultPropertiesFileName = "local.defaults.properties" }
5. Google Maps hinzufügen
In diesem Abschnitt fügen Sie eine Google-Karte hinzu, die beim Starten der App geladen wird.
Maps Compose-Abhängigkeiten hinzufügen
Nachdem auf Ihren API-Schlüssel in der App zugegriffen werden kann, müssen Sie als Nächstes die Maps SDK for Android-Abhängigkeit in die build.gradle.kts
-Datei Ihrer App einfügen. Wenn Sie Jetpack Compose verwenden möchten, nutzen Sie die Maps Compose-Bibliothek, die Elemente des Maps SDK for Android als zusammensetzbare Funktionen und Datentypen bereitstellt.
build.gradle.kts
Ersetzen Sie in der Datei build.gradle.kts
auf App-Ebene die Maps SDK for Android-Abhängigkeiten, die nicht auf Compose basieren:
dependencies {
// ...
// Google Maps SDK -- these are here for the data model. Remove these dependencies and replace
// with the compose versions.
implementation("com.google.android.gms:play-services-maps:18.2.0")
// KTX for the Maps SDK for Android library
implementation("com.google.maps.android:maps-ktx:5.0.0")
// KTX for the Maps SDK for Android Utility Library
implementation("com.google.maps.android:maps-utils-ktx:5.0.0")
}
mit ihren zusammensetzbaren Pendants:
dependencies {
// ...
// Google Maps Compose library
val mapsComposeVersion = "4.4.1"
implementation("com.google.maps.android:maps-compose:$mapsComposeVersion")
// Google Maps Compose utility library
implementation("com.google.maps.android:maps-compose-utils:$mapsComposeVersion")
// Google Maps Compose widgets library
implementation("com.google.maps.android:maps-compose-widgets:$mapsComposeVersion")
}
Composable für Google Maps hinzufügen
Fügen Sie in MountainMap.kt
die GoogleMap
-Composable in die Box
-Composable ein, die in der MapMountain
-Composable verschachtelt ist.
import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.GoogleMapComposable
// ...
@Composable
fun MountainMap(
paddingValues: PaddingValues,
viewState: MountainsScreenViewState.MountainList,
eventFlow: Flow<MountainsScreenEvent>,
selectedMarkerType: MarkerType,
) {
var isMapLoaded by remember { mutableStateOf(false) }
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
// Add GoogleMap here
GoogleMap(
modifier = Modifier.fillMaxSize(),
onMapLoaded = { isMapLoaded = true }
)
// ...
}
}
Erstellen Sie nun die App und führen Sie sie aus. Es sollte eine Karte mit dem berüchtigten Null Island (auch als Breiten- und Längengrad 0 bezeichnet) in der Mitte angezeigt werden. Später erfahren Sie, wie Sie die Karte auf den gewünschten Standort und die gewünschte Zoomstufe ausrichten. Jetzt können Sie aber erst einmal Ihren ersten Erfolg feiern.
6. Cloudbasiertes Gestalten von Karteninhalten
Sie können den Stil Ihrer Karte mit cloudbasiertem Gestalten von Karteninhalten anpassen.
Karten-ID erstellen
Wenn Sie noch keine Karten-ID mit einem zugehörigen Kartenstil erstellt haben, folgen Sie der Anleitung unter Karten-IDs, um die folgenden Schritte auszuführen:
- Erstellen Sie eine Karten-ID.
- Verknüpfen Sie eine Karten-ID mit einem Kartenstil.
Karten-ID in App einbinden
Wenn Sie die von Ihnen erstellte Karten-ID verwenden möchten, geben Sie sie beim Instanziieren der zusammensetzbaren Funktion GoogleMap
an, indem Sie ein GoogleMapOptions
-Objekt erstellen, das dem Parameter googleMapOptionsFactory
im Konstruktor zugewiesen wird.
GoogleMap(
// ...
googleMapOptionsFactory = {
GoogleMapOptions().mapId("MyMapId")
}
)
Wenn Sie das erledigt haben, können Sie die App ausführen, um Ihre Karte im ausgewählten Stil zu sehen.
7. Markierungsdaten laden
Die Hauptaufgabe der App besteht darin, eine Sammlung von Bergen aus dem lokalen Speicher zu laden und diese Berge in der GoogleMap
anzuzeigen. In diesem Schritt sehen Sie sich die bereitgestellte Infrastruktur zum Laden der Berggipfeldaten und zur Darstellung in der Benutzeroberfläche an.
Berg
Die Datenklasse Mountain
enthält alle Daten zu den einzelnen Bergen.
data class Mountain(
val id: Int,
val name: String,
val location: LatLng,
val elevation: Meters,
)
Die Berge werden später nach ihrer Höhe partitioniert. Berge, die mindestens 14.000 Fuß hoch sind, werden als Fourteeners bezeichnet. Der Startcode enthält eine Erweiterungsfunktion, die diese Prüfung für Sie durchführt.
/**
* Extension function to determine whether a mountain is a "14er", i.e., has an elevation greater
* than 14,000 feet (~4267 meters).
*/
fun Mountain.is14er() = elevation >= 14_000.feet
MountainsScreenViewState
Die Klasse MountainsScreenViewState
enthält alle Daten, die zum Rendern der Ansicht erforderlich sind. Der Status kann entweder Loading
oder MountainList
sein, je nachdem, ob die Liste der Berge geladen wurde.
/**
* Sealed class representing the state of the mountain map view.
*/
sealed class MountainsScreenViewState {
data object Loading : MountainsScreenViewState()
data class MountainList(
// List of the mountains to display
val mountains: List<Mountain>,
// Bounding box that contains all of the mountains
val boundingBox: LatLngBounds,
// Switch indicating whether all the mountains or just the 14ers
val showingAllPeaks: Boolean = false,
) : MountainsScreenViewState()
}
Bereitgestellte Klassen: MountainsRepository
und MountainsViewModel
Im Starterprojekt ist die Klasse MountainsRepository
bereits vorhanden. Diese Klasse liest eine Liste von Bergorten, die in einer GPS Exchange Format
- oder GPX-Datei (top_peaks.gpx
) gespeichert sind. Der Aufruf von mountainsRepository.loadMountains()
gibt ein StateFlow<List<Mountain>>
zurück.
MountainsRepository
class MountainsRepository(@ApplicationContext val context: Context) {
private val _mountains = MutableStateFlow(emptyList<Mountain>())
val mountains: StateFlow<List<Mountain>> = _mountains
private var loaded = false
/**
* Loads the list of mountains from the list of mountains from the raw resource.
*/
suspend fun loadMountains(): StateFlow<List<Mountain>> {
if (!loaded) {
loaded = true
_mountains.value = withContext(Dispatchers.IO) {
context.resources.openRawResource(R.raw.top_peaks).use { inputStream ->
readMountains(inputStream)
}
}
}
return mountains
}
/**
* Reads the [Waypoint]s from the given [inputStream] and returns a list of [Mountain]s.
*/
private fun readMountains(inputStream: InputStream) =
readWaypoints(inputStream).mapIndexed { index, waypoint ->
waypoint.toMountain(index)
}.toList()
// ...
}
MountainsViewModel
MountainsViewModel
ist eine ViewModel
-Klasse, die die Sammlungen von Bergen lädt und diese Sammlungen sowie andere Teile des UI-Zustands über mountainsScreenViewState
bereitstellt. mountainsScreenViewState
ist ein heißen StateFlow
, der von der Benutzeroberfläche als veränderlicher Status mit der Erweiterungsfunktion collectAsState
beobachtet werden kann.
Gemäß den Prinzipien der Softwarearchitektur enthält MountainsViewModel
den gesamten Status der App. Die Benutzeroberfläche sendet Nutzerinteraktionen mit der Methode onEvent
an das View-Modell.
@HiltViewModel
class MountainsViewModel
@Inject
constructor(
mountainsRepository: MountainsRepository
) : ViewModel() {
private val _eventChannel = Channel<MountainsScreenEvent>()
// Event channel to send events to the UI
internal fun getEventChannel() = _eventChannel.receiveAsFlow()
// Whether or not to show all of the high peaks
private var showAllMountains = MutableStateFlow(false)
val mountainsScreenViewState =
mountainsRepository.mountains.combine(showAllMountains) { allMountains, showAllMountains ->
if (allMountains.isEmpty()) {
MountainsScreenViewState.Loading
} else {
val filteredMountains =
if (showAllMountains) allMountains else allMountains.filter { it.is14er() }
val boundingBox = filteredMountains.map { it.location }.toLatLngBounds()
MountainsScreenViewState.MountainList(
mountains = filteredMountains,
boundingBox = boundingBox,
showingAllPeaks = showAllMountains,
)
}
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = MountainsScreenViewState.Loading
)
init {
// Load the full set of mountains
viewModelScope.launch {
mountainsRepository.loadMountains()
}
}
// Handle user events
fun onEvent(event: MountainsViewModelEvent) {
when (event) {
OnZoomAll -> onZoomAll()
OnToggleAllPeaks -> toggleAllPeaks()
}
}
private fun onZoomAll() {
sendScreenEvent(MountainsScreenEvent.OnZoomAll)
}
private fun toggleAllPeaks() {
showAllMountains.value = !showAllMountains.value
}
// Send events back to the UI via the event channel
private fun sendScreenEvent(event: MountainsScreenEvent) {
viewModelScope.launch { _eventChannel.send(event) }
}
}
Wenn Sie mehr über die Implementierung dieser Klassen erfahren möchten, können Sie auf GitHub darauf zugreifen oder die Klassen MountainsRepository
und MountainsViewModel
in Android Studio öffnen.
ViewModel verwenden
Das Ansichtsmodell wird in MainActivity
verwendet, um den viewState
abzurufen. Sie verwenden viewState
später in diesem Codelab, um die Markierungen zu rendern. Dieser Code ist bereits im Starterprojekt enthalten und wird hier nur als Referenz angezeigt.
val viewModel: MountainsViewModel by viewModels()
val screenViewState = viewModel.mountainsScreenViewState.collectAsState()
val viewState = screenViewState.value
8. Kamera positionieren
Bei der Standardeinstellung für GoogleMap
wird die Mitte auf den Breiten- und Längengrad 0 gesetzt. Die Markierungen, die Sie rendern, befinden sich im US-Bundesstaat Colorado. Die von der Ansicht bereitgestellte viewState
enthält eine LatLngBounds-Klasse, die alle Markierungen enthält.
Erstellen Sie in MountainMap.kt
ein CameraPositionState
, das auf den Mittelpunkt des Begrenzungsrahmens initialisiert wird. Legen Sie den Parameter cameraPositionState
des GoogleMap
auf die Variable cameraPositionState
fest, die Sie gerade erstellt haben.
fun MountainMap(
// ...
) {
// ...
val cameraPositionState = rememberCameraPositionState {
position = CameraPosition.fromLatLngZoom(viewState.boundingBox.center, 5f)
}
GoogleMap(
// ...
cameraPositionState = cameraPositionState,
)
}
Führen Sie den Code jetzt aus. Die Karte wird auf Colorado zentriert.
Auf die Ausdehnung der Markierung zoomen
Damit die Karte wirklich auf die Markierungen fokussiert wird, fügen Sie die Funktion zoomAll
am Ende der Datei MountainMap.kt
hinzu. Für diese Funktion ist ein CoroutineScope
erforderlich, da die Animation der Kamera an einen neuen Ort ein asynchroner Vorgang ist, der Zeit in Anspruch nimmt.
fun zoomAll(
scope: CoroutineScope,
cameraPositionState: CameraPositionState,
boundingBox: LatLngBounds
) {
scope.launch {
cameraPositionState.animate(
update = CameraUpdateFactory.newLatLngBounds(boundingBox, 64),
durationMs = 1000
)
}
}
Fügen Sie als Nächstes Code hinzu, um die Funktion zoomAll
aufzurufen, wenn sich die Grenzen um die Markierungssammlung ändern oder wenn der Nutzer in der Top-App-Leiste auf die Schaltfläche „Zoomgrenzen“ klickt. Die Schaltfläche zum Zoomen ist bereits so konfiguriert, dass Ereignisse an das Ansichtsmodell gesendet werden. Sie müssen diese Ereignisse nur aus dem Ansichtsmodell erfassen und als Reaktion darauf die Funktion zoomAll
aufrufen.
fun MountainMap(
// ...
) {
// ...
val scope = rememberCoroutineScope()
LaunchedEffect(key1 = viewState.boundingBox) {
zoomAll(scope, cameraPositionState, viewState.boundingBox)
}
LaunchedEffect(true) {
eventFlow.collect { event ->
when (event) {
MountainsScreenEvent.OnZoomAll -> {
zoomAll(scope, cameraPositionState, viewState.boundingBox)
}
}
}
}
}
Wenn Sie die App jetzt ausführen, wird die Karte auf den Bereich fokussiert, in dem die Markierungen platziert werden. Sie können die Karte neu positionieren und den Zoom ändern. Wenn Sie auf die Schaltfläche „Zoomgrenzen“ klicken, wird die Karte wieder auf den Bereich mit den Markierungen fokussiert. Das ist ein Fortschritt! Auf der Karte sollte aber auch etwas zu sehen sein. Das ist der nächste Schritt.
9. Einfache Markierungen
In diesem Schritt fügen Sie der Karte Markierungen hinzu, die POIs darstellen, die Sie auf der Karte hervorheben möchten. Sie verwenden die Liste der Berge, die im Starterprojekt enthalten ist, und fügen diese Orte als Markierungen auf der Karte hinzu.
Fügen Sie zuerst einen Inhaltsblock in GoogleMap
ein. Es gibt mehrere Markertypen. Fügen Sie daher eine when
-Anweisung hinzu, um zu jedem Typ zu verzweigen. Die einzelnen Typen werden dann in den folgenden Schritten implementiert.
GoogleMap(
// ...
) {
when (selectedMarkerType) {
MarkerType.Basic -> {
BasicMarkersMapContent(
mountains = viewState.mountains,
)
}
MarkerType.Advanced -> {
AdvancedMarkersMapContent(
mountains = viewState.mountains,
)
}
MarkerType.Clustered -> {
ClusteringMarkersMapContent(
mountains = viewState.mountains,
)
}
}
}
Markierungen hinzufügen
Kommentieren Sie BasicMarkersMapContent
mit @GoogleMapComposable
. Sie können im Inhaltsblock GoogleMap
nur @GoogleMapComposable
-Funktionen verwenden. Das mountains
-Objekt enthält eine Liste von Mountain
-Objekten. Sie fügen für jeden Berg in dieser Liste eine Markierung hinzu und verwenden dazu den Ort, den Namen und die Höhe aus dem Mountain
-Objekt. Der Standort wird verwendet, um den Statusparameter von Marker
festzulegen, der wiederum die Position der Markierung steuert.
// ...
import com.google.android.gms.maps.model.Marker
import com.google.maps.android.compose.GoogleMapComposable
import com.google.maps.android.compose.Marker
import com.google.maps.android.compose.rememberMarkerState
@Composable
@GoogleMapComposable
fun BasicMarkersMapContent(
mountains: List<Mountain>,
onMountainClick: (Marker) -> Boolean = { false }
) {
mountains.forEach { mountain ->
Marker(
state = rememberMarkerState(position = mountain.location),
title = mountain.name,
snippet = mountain.elevation.toElevationString(),
tag = mountain,
onClick = { marker ->
onMountainClick(marker)
false
},
zIndex = if (mountain.is14er()) 5f else 2f
)
}
}
Führen Sie die App aus. Sie sehen dann die Markierungen, die Sie gerade hinzugefügt haben.
Markierungen anpassen
Es gibt verschiedene Anpassungsoptionen für Markierungen, die Sie gerade hinzugefügt haben. So können Sie sie hervorheben und Nutzern nützliche Informationen liefern. In dieser Aufgabe passen Sie das Bild für jede Markierung an.
Das Starterprojekt enthält die Hilfsfunktion vectorToBitmap
, mit der BitmapDescriptor
s aus einem @DrawableResource
erstellt werden.
Der Starter-Code enthält ein Bergsymbol, baseline_filter_hdr_24.xml
, mit dem Sie die Markierungen anpassen können.
Die Funktion vectorToBitmap
konvertiert eine Vektorgrafik in ein BitmapDescriptor
zur Verwendung mit der Maps-Bibliothek. Die Symbolfarben werden mit einer BitmapParameters
-Instanz festgelegt.
data class BitmapParameters(
@DrawableRes val id: Int,
@ColorInt val iconColor: Int,
@ColorInt val backgroundColor: Int? = null,
val backgroundAlpha: Int = 168,
val padding: Int = 16,
)
fun vectorToBitmap(context: Context, parameters: BitmapParameters): BitmapDescriptor {
// ...
}
Verwenden Sie die Funktion vectorToBitmap
, um zwei benutzerdefinierte BitmapDescriptor
s zu erstellen: eine für Berge mit einer Höhe von mindestens 14.000 Fuß und eine für normale Berge. Verwenden Sie dann den Parameter icon
der zusammensetzbaren Funktion Marker
, um das Symbol festzulegen. Mit dem Parameter anchor
können Sie die Ankerposition relativ zum Symbol ändern. Bei diesen kreisförmigen Symbolen ist es besser, den Mittelpunkt zu verwenden.
@Composable
@GoogleMapComposable
fun BasicMarkersMapContent(
// ...
) {
// Create mountainIcon and fourteenerIcon
val mountainIcon = vectorToBitmap(
LocalContext.current,
BitmapParameters(
id = R.drawable.baseline_filter_hdr_24,
iconColor = MaterialTheme.colorScheme.secondary.toArgb(),
backgroundColor = MaterialTheme.colorScheme.secondaryContainer.toArgb(),
)
)
val fourteenerIcon = vectorToBitmap(
LocalContext.current,
BitmapParameters(
id = R.drawable.baseline_filter_hdr_24,
iconColor = MaterialTheme.colorScheme.onPrimary.toArgb(),
backgroundColor = MaterialTheme.colorScheme.primary.toArgb(),
)
)
mountains.forEach { mountain ->
val icon = if (mountain.is14er()) fourteenerIcon else mountainIcon
Marker(
// ...
anchor = Offset(0.5f, 0.5f),
icon = icon,
)
}
}
Führen Sie die App aus und sehen Sie sich die benutzerdefinierten Markierungen an. Stellen Sie den Schalter Show all
auf „Ein“, um alle Berge zu sehen. Die Berge haben je nachdem, ob es sich um einen Viertausender handelt, unterschiedliche Markierungen.
10. Erweiterte Markierungen
AdvancedMarker
s bieten zusätzliche Funktionen für die grundlegenden Markers
. In diesem Schritt legen Sie das Kollisionsverhalten fest und konfigurieren den Pin-Stil.
Fügen Sie @GoogleMapComposable
zur Funktion AdvancedMarkersMapContent
hinzu. Wiederholen Sie die mountains
und fügen Sie für jede ein AdvancedMarker
hinzu.
@Composable
@GoogleMapComposable
fun AdvancedMarkersMapContent(
mountains: List<Mountain>,
onMountainClick: (Marker) -> Boolean = { false },
) {
mountains.forEach { mountain ->
AdvancedMarker(
state = rememberMarkerState(position = mountain.location),
title = mountain.name,
snippet = mountain.elevation.toElevationString(),
collisionBehavior = AdvancedMarkerOptions.CollisionBehavior.REQUIRED_AND_HIDES_OPTIONAL,
onClick = { marker ->
onMountainClick(marker)
false
}
)
}
}
Beachten Sie den Parameter collisionBehavior
. Wenn Sie diesen Parameter auf REQUIRED_AND_HIDES_OPTIONAL
setzen, wird Ihre Markierung durch Markierungen mit niedrigerer Priorität ersetzt. Das lässt sich gut erkennen, wenn Sie einen einfachen Marker mit einem erweiterten Marker vergleichen. Die Standardmarkierung befindet sich wahrscheinlich an derselben Stelle wie Ihre Markierung auf der Basiskarte. Die erweiterte Markierung führt dazu, dass die Markierung mit niedrigerer Priorität ausgeblendet wird.
Führen Sie die App aus, um die erweiterten Markierungen zu sehen. Achten Sie darauf, dass Sie in der unteren Navigationsleiste den Tab Advanced markers
auswählen.
Angepasste AdvancedMarkers
Die Symbole verwenden die primären und sekundären Farbschemas, um zwischen den „Fourteeners“ und anderen Bergen zu unterscheiden. Verwenden Sie die Funktion vectorToBitmap
, um zwei BitmapDescriptor
s zu erstellen: eines für die Berge mit einer Höhe von mindestens 14.000 Fuß und eines für die anderen Berge. Erstellen Sie mit diesen Symbolen für jeden Typ ein benutzerdefiniertes pinConfig
. Wenden Sie den Pin schließlich auf die entsprechende AdvancedMarker
basierend auf der Funktion is14er()
an.
@Composable
@GoogleMapComposable
fun AdvancedMarkersMapContent(
mountains: List<Mountain>,
onMountainClick: (Marker) -> Boolean = { false },
) {
val mountainIcon = vectorToBitmap(
LocalContext.current,
BitmapParameters(
id = R.drawable.baseline_filter_hdr_24,
iconColor = MaterialTheme.colorScheme.onSecondary.toArgb(),
)
)
val mountainPin = with(PinConfig.builder()) {
setGlyph(PinConfig.Glyph(mountainIcon))
setBackgroundColor(MaterialTheme.colorScheme.secondary.toArgb())
setBorderColor(MaterialTheme.colorScheme.onSecondary.toArgb())
build()
}
val fourteenerIcon = vectorToBitmap(
LocalContext.current,
BitmapParameters(
id = R.drawable.baseline_filter_hdr_24,
iconColor = MaterialTheme.colorScheme.onPrimary.toArgb(),
)
)
val fourteenerPin = with(PinConfig.builder()) {
setGlyph(PinConfig.Glyph(fourteenerIcon))
setBackgroundColor(MaterialTheme.colorScheme.primary.toArgb())
setBorderColor(MaterialTheme.colorScheme.onPrimary.toArgb())
build()
}
mountains.forEach { mountain ->
val pin = if (mountain.is14er()) fourteenerPin else mountainPin
AdvancedMarker(
state = rememberMarkerState(position = mountain.location),
title = mountain.name,
snippet = mountain.elevation.toElevationString(),
collisionBehavior = AdvancedMarkerOptions.CollisionBehavior.REQUIRED_AND_HIDES_OPTIONAL,
pinConfig = pin,
onClick = { marker ->
onMountainClick(marker)
false
}
)
}
}
11. Marker-Cluster
In diesem Schritt verwenden Sie die zusammensetzbare Funktion Clustering
, um die zoomabhängige Gruppierung von Elementen hinzuzufügen.
Für die zusammensetzbare Funktion Clustering
ist eine Sammlung von ClusterItem
-Objekten erforderlich. MountainClusterItem
implementiert die ClusterItem
-Schnittstelle. Fügen Sie diese Klasse in die Datei ClusteringMarkersMapContent.kt
ein.
data class MountainClusterItem(
val mountain: Mountain,
val snippetString: String
) : ClusterItem {
override fun getPosition() = mountain.location
override fun getTitle() = mountain.name
override fun getSnippet() = snippetString
override fun getZIndex() = 0f
}
Fügen Sie nun den Code hinzu, um MountainClusterItem
-Objekte aus der Liste der Berge zu erstellen. In diesem Code wird UnitsConverter
verwendet, um die Anzeigeeinheiten basierend auf dem Gebietsschema des Nutzers in die für ihn geeigneten Einheiten umzuwandeln. Dies wird in MainActivity
mit einem CompositionLocal
eingerichtet.
@OptIn(MapsComposeExperimentalApi::class)
@Composable
@GoogleMapComposable
fun ClusteringMarkersMapContent(
mountains: List<Mountain>,
// ...
) {
val unitsConverter = LocalUnitsConverter.current
val resources = LocalContext.current.resources
val mountainClusterItems by remember(mountains) {
mutableStateOf(
mountains.map { mountain ->
MountainClusterItem(
mountain = mountain,
snippetString = unitsConverter.toElevationString(resources, mountain.elevation)
)
}
)
}
Clustering(
items = mountainClusterItems,
)
}
Mit diesem Code werden die Markierungen basierend auf der Zoomstufe gruppiert. Schön ordentlich!
Cluster anpassen
Wie bei den anderen Markierungstypen können Sie auch Cluster-Markierungen anpassen. Mit dem Parameter clusterItemContent
der zusammensetzbaren Funktion Clustering
wird ein benutzerdefinierter zusammensetzbarer Block zum Rendern eines nicht gruppierten Elements festgelegt. Implementieren Sie eine @Composable
-Funktion, um die Markierung zu erstellen. Mit der Funktion SingleMountain
wird ein zusammensetzbares Material 3-Element Icon
mit einem benutzerdefinierten Hintergrundfarbschema gerendert.
Erstellen Sie in ClusteringMarkersMapContent.kt
eine Datenklasse, die das Farbschema für eine Markierung definiert:
data class IconColor(val iconColor: Color, val backgroundColor: Color, val borderColor: Color)
Erstellen Sie außerdem in ClusteringMarkersMapContent.kt
eine zusammensetzbare Funktion zum Rendern eines Symbols für ein bestimmtes Farbschema:
@Composable
private fun SingleMountain(
colors: IconColor,
) {
Icon(
painterResource(id = R.drawable.baseline_filter_hdr_24),
tint = colors.iconColor,
contentDescription = "",
modifier = Modifier
.size(32.dp)
.padding(1.dp)
.drawBehind {
drawCircle(color = colors.backgroundColor, style = Fill)
drawCircle(color = colors.borderColor, style = Stroke(width = 3f))
}
.padding(4.dp)
)
}
Erstellen Sie nun ein Farbschema für die Berge mit einer Höhe von mindestens 14.000 Fuß und ein weiteres Farbschema für die anderen Berge. Wählen Sie im clusterItemContent
-Block das Farbschema aus, je nachdem, ob der angegebene Berg ein Viertausender ist oder nicht.
fun ClusteringMarkersMapContent(
mountains: List<Mountain>,
// ...
) {
// ...
val backgroundAlpha = 0.6f
val fourteenerColors = IconColor(
iconColor = MaterialTheme.colorScheme.onPrimary,
backgroundColor = MaterialTheme.colorScheme.primary.copy(alpha = backgroundAlpha),
borderColor = MaterialTheme.colorScheme.primary
)
val otherColors = IconColor(
iconColor = MaterialTheme.colorScheme.secondary,
backgroundColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = backgroundAlpha),
borderColor = MaterialTheme.colorScheme.secondary
)
// ...
Clustering(
items = mountainClusterItems,
clusterItemContent = { mountainItem ->
val colors = if (mountainItem.mountain.is14er()) {
fourteenerColors
} else {
otherColors
}
SingleMountain(colors)
},
)
}
Führen Sie die App jetzt aus, um angepasste Versionen der einzelnen Elemente zu sehen.
12. Auf Karten zeichnen
Sie haben bereits eine Möglichkeit kennengelernt, auf der Karte zu zeichnen (durch Hinzufügen von Markierungen). Das Maps SDK for Android unterstützt jedoch zahlreiche weitere Möglichkeiten, um nützliche Informationen auf der Karte darzustellen.
Wenn Sie beispielsweise Routen und Gebiete auf der Karte darstellen möchten, können Sie Polyline
und Polygon
verwenden, um diese auf der Karte anzuzeigen. Wenn Sie ein Bild auf der Erdoberfläche fixieren möchten, können Sie ein GroundOverlay
verwenden.
In dieser Aufgabe lernen Sie, wie Sie Formen zeichnen, insbesondere eine Umrisslinie um den Bundesstaat Colorado. Die Grenze von Colorado liegt zwischen 37° N und 41° N und zwischen 102°03′ W und 109°03′ W. Das macht das Zeichnen des Umrisses recht einfach.
Der Startcode enthält die DMS
-Klasse, mit der die Notation in Grad, Minuten und Sekunden in Dezimalgrad umgewandelt werden kann.
enum class Direction(val sign: Int) {
NORTH(1),
EAST(1),
SOUTH(-1),
WEST(-1)
}
/**
* Degrees, minutes, seconds utility class
*/
data class DMS(
val direction: Direction,
val degrees: Double,
val minutes: Double = 0.0,
val seconds: Double = 0.0,
)
fun DMS.toDecimalDegrees(): Double =
(degrees + (minutes / 60) + (seconds / 3600)) * direction.sign
Mit der DMS-Klasse können Sie die Grenze von Colorado zeichnen, indem Sie die vier Eckpunkte LatLng
definieren und als Polygon
rendern. Fügen Sie den folgenden Code zu MountainMap.kt
hinzu.
@Composable
@GoogleMapComposable
fun ColoradoPolygon() {
val north = 41.0
val south = 37.0
val east = DMS(WEST, 102.0, 3.0).toDecimalDegrees()
val west = DMS(WEST, 109.0, 3.0).toDecimalDegrees()
val locations = listOf(
LatLng(north, east),
LatLng(south, east),
LatLng(south, west),
LatLng(north, west),
)
Polygon(
points = locations,
strokeColor = MaterialTheme.colorScheme.tertiary,
strokeWidth = 3F,
fillColor = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.3f),
)
}
Rufen Sie nun ColoradoPolyon()
innerhalb des Inhaltsblocks GoogleMap
auf.
@Composable
fun MountainMap(
// ...
) {
Box(
// ...
) {
GoogleMap(
// ...
) {
ColoradoPolygon()
}
}
}
Die App zeigt nun den Umriss des US-Bundesstaates Colorado mit einer dezenten Füllung an.
13. KML-Ebene und Maßstabsleiste hinzufügen
In diesem letzten Abschnitt skizzieren Sie die verschiedenen Gebirgszüge und fügen der Karte eine Maßstabsleiste hinzu.
Gebirgsketten umreißen
Zuvor haben Sie eine Umrisslinie um Colorado gezeichnet. Hier fügen Sie der Karte komplexere Formen hinzu. Der Startcode enthält eine KML-Datei (Keyhole Markup Language), in der die wichtigsten Gebirgszüge grob umrissen sind. Die Maps SDK for Android-Dienstprogrammbibliothek enthält eine Funktion zum Hinzufügen einer KML-Ebene zur Karte. Fügen Sie in MountainMap.kt
nach dem when
-Block einen MapEffect
-Aufruf im GoogleMap
-Inhaltsblock hinzu. Die Funktion MapEffect
wird mit einem GoogleMap
-Objekt aufgerufen. Sie kann als nützliche Brücke zwischen nicht zusammensetzbaren APIs und Bibliotheken dienen, die ein GoogleMap
-Objekt erfordern.
fun MountainMap(
// ...
) {
var isMapLoaded by remember { mutableStateOf(false) }
val context = LocalContext.current
GoogleMap(
// ...
) {
// ...
when (selectedMarkerType) {
// ...
}
// This code belongs inside the GoogleMap content block, but outside of
// the 'when' statement
MapEffect(key1 = true) {map ->
val layer = KmlLayer(map, R.raw.mountain_ranges, context)
layer.addLayerToMap()
}
}
Maßstabsleiste hinzufügen
Als letzte Aufgabe fügen Sie der Karte eine Maßstabsleiste hinzu. Das ScaleBar
implementiert eine zusammensetzbare Skala, die der Karte hinzugefügt werden kann. Beachten Sie, dass ScaleBar
kein ist.
@GoogleMapComposable
und kann daher nicht den GoogleMap
-Inhalten hinzugefügt werden. Stattdessen fügen Sie sie dem Box
hinzu, das die Karte enthält.
Box(
// ...
) {
GoogleMap(
// ...
) {
// ...
}
ScaleBar(
modifier = Modifier
.padding(top = 5.dp, end = 15.dp)
.align(Alignment.TopEnd),
cameraPositionState = cameraPositionState
)
// ...
}
Führen Sie die App aus, um das vollständig implementierte Codelab zu sehen.
14. Lösungscode abrufen
Mit den folgenden Befehlen können Sie den Code für das fertige Codelab herunterladen:
- Klonen Sie das Repository, wenn Sie
git
installiert haben.
$ git clone https://github.com/googlemaps-samples/codelab-maps-platform-101-compose.git
Alternativ können Sie auf die folgende Schaltfläche klicken, um den Quellcode herunterzuladen.
- Nachdem Sie den Code erhalten haben, öffnen Sie das Projekt im Verzeichnis
solution
in Android Studio.
15. Glückwunsch
Glückwunsch! Wir haben viele Inhalte behandelt und hoffentlich haben Sie jetzt ein besseres Verständnis der wichtigsten Funktionen des Maps SDK for Android.
Weitere Informationen
- Maps SDK for Android: Dynamische, interaktive, benutzerdefinierte Karten, Standortdaten und raumbezogene Daten in Ihre Android-Apps einbinden
- Maps Compose-Bibliothek: Eine Reihe von zusammensetzbaren Open-Source-Funktionen und Datentypen, die Sie mit Jetpack Compose zum Erstellen Ihrer App verwenden können.
- android-maps-compose: Beispielcode auf GitHub, der alle in diesem Codelab behandelten Funktionen und mehr demonstriert.
- Weitere Kotlin-Codelabs für die Entwicklung von Android-Apps mit der Google Maps Platform