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, die eine Karte mit Fahrradgeschäften in San Francisco, Kalifornien, USA, anzeigt.
Vorbereitung
- Grundkenntnisse in Kotlin und Android-Entwicklung
Aufgaben
- Aktivieren und verwenden Sie das Maps SDK for Android, um Google Maps in eine Android-App einzufügen.
- Markierungen hinzufügen, anpassen und gruppieren
- Polylinien und Polygone auf der Karte zeichnen
- Den Blickwinkel der Kamera programmatisch steuern.
Voraussetzungen
- Maps SDK for Android
- Ein Google-Konto mit aktivierter Abrechnung
- Android Studio 2020.3.1 oder höher
- Google Play-Dienste in Android Studio installiert
- Ein Android-Gerät oder ein Android-Emulator, auf dem die Google APIs-Plattform auf Basis von Android 4.2.2 oder höher ausgeführt wird (Installationsanleitung).
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, aber wenn Sie alle Schritte nachvollziehen möchten, um sie selbst zu erstellen, lesen Sie weiter.
- Klonen Sie das Repository, wenn Sie
git
installiert haben.
git clone https://github.com/googlecodelabs/maps-platform-101-android.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. Google Maps hinzufügen
In diesem Abschnitt fügen Sie Google Maps hinzu, damit die Karte beim Starten der App geladen wird.
Eigenen API-Schlüssel hinzufügen
Der API-Schlüssel, den Sie in einem vorherigen Schritt erstellt haben, muss der App zur Verfügung gestellt werden, damit das Maps SDK for Android Ihren Schlüssel mit Ihrer App verknüpfen kann.
- Öffnen Sie dazu die Datei
local.properties
im Stammverzeichnis Ihres Projekts (auf derselben Ebene wiegradle.properties
undsettings.gradle
). - Definieren Sie in dieser Datei einen neuen Schlüssel
GOOGLE_MAPS_API_KEY
mit dem von Ihnen erstellten API-Schlüssel als Wert.
local.properties
GOOGLE_MAPS_API_KEY=YOUR_KEY_HERE
Beachten Sie, dass local.properties
in der Datei .gitignore
im Git-Repository aufgeführt ist. Das liegt daran, dass Ihr API-Schlüssel als vertrauliche Information gilt und nach Möglichkeit nicht in die Quellcodeverwaltung eingecheckt werden sollte.
- Als Nächstes müssen Sie das Secrets Gradle-Plug-in für Android in die
build.gradle
-Datei Ihrer App im Verzeichnisapp/
einfügen und die folgende Zeile in denplugins
-Block einfügen, damit Ihre API in der gesamten App verwendet werden kann:
build.gradle-Datei auf App-Ebene
plugins {
// ...
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
}
Außerdem müssen Sie die Datei build.gradle
auf Projektebene so ändern, dass sie den folgenden Klassenpfad enthält:
build.gradle-Datei auf Projektebene
buildscript {
dependencies {
// ...
classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:1.3.0"
}
}
Dieses Plug-in stellt Schlüssel, die Sie in der Datei local.properties
definiert haben, beim Build-Vorgang als Build-Variablen in der Android-Manifestdatei und als Variablen in der von Gradle generierten BuildConfig
-Klasse zur Verfügung. Durch die Verwendung dieses Plug-ins wird der Boilerplate-Code entfernt, der ansonsten erforderlich wäre, um Eigenschaften aus local.properties
zu lesen, damit in Ihrer gesamten App darauf zugegriffen werden kann.
Google Maps-Abhängigkeit 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
-Datei Ihrer App einfügen.
Im Starterprojekt dieses Codelabs wurde diese Abhängigkeit bereits für Sie hinzugefügt.
build.gradle
dependencies {
// Dependency to include Maps SDK for Android
implementation 'com.google.android.gms:play-services-maps:17.0.0'
}
- Fügen Sie als Nächstes in
AndroidManifest.xml
ein neuesmeta-data
-Tag hinzu, um den API-Schlüssel zu übergeben, den Sie in einem früheren Schritt erstellt haben. Öffnen Sie dazu diese Datei in Android Studio und fügen Sie das folgendemeta-data
-Tag in dasapplication
-Objekt in der DateiAndroidManifest.xml
ein, die sich inapp/src/main
befindet.
AndroidManifest.xml
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${GOOGLE_MAPS_API_KEY}" />
- Erstellen Sie als Nächstes im Verzeichnis
app/src/main/res/layout/
eine neue Layoutdatei mit dem Namenactivity_main.xml
und definieren Sie sie so:
activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<fragment
class="com.google.android.gms.maps.SupportMapFragment"
android:id="@+id/map_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Dieses Layout hat ein einzelnes FrameLayout
, das ein SupportMapFragment
enthält. Dieses Fragment enthält das zugrunde liegende GoogleMaps
-Objekt, das Sie in späteren Schritten verwenden.
- Aktualisieren Sie zum Schluss die Klasse
MainActivity
inapp/src/main/java/com/google/codelabs/buildyourfirstmap
, indem Sie den folgenden Code hinzufügen, um die MethodeonCreate
zu überschreiben. So können Sie ihren Inhalt mit dem neuen Layout festlegen, das Sie gerade erstellt haben.
MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
- Führen Sie die App nun aus. Die Karte sollte jetzt auf dem Bildschirm Ihres Geräts geladen werden.
5. Cloudbasiertes Gestalten von Karteninhalten (optional)
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 zur App hinzufügen
Wenn Sie die erstellte Karten-ID verwenden möchten, ändern Sie die Datei activity_main.xml
und übergeben Sie Ihre Karten-ID im Attribut map:mapId
des SupportMapFragment
.
activity_main.xml
<fragment xmlns:map="http://schemas.android.com/apk/res-auto"
class="com.google.android.gms.maps.SupportMapFragment"
<!-- ... -->
map:mapId="YOUR_MAP_ID" />
Wenn Sie das erledigt haben, können Sie die App ausführen, um Ihre Karte im ausgewählten Stil zu sehen.
6. Markierungen hinzufügen
In dieser Aufgabe fügen Sie der Karte Markierungen hinzu, die POIs darstellen, die Sie auf der Karte hervorheben möchten. Zuerst rufen Sie eine Liste von Orten ab, die im Starterprojekt für Sie bereitgestellt wurden, und fügen diese Orte dann der Karte hinzu. In diesem Beispiel sind das Fahrradgeschäfte.
Referenz auf GoogleMap abrufen
Zuerst müssen Sie einen Verweis auf das GoogleMap
-Objekt abrufen, damit Sie seine Methoden verwenden können. Fügen Sie dazu den folgenden Code in Ihre MainActivity.onCreate()
-Methode direkt nach dem Aufruf von setContentView()
ein:
MainActivity.onCreate()
val mapFragment = supportFragmentManager.findFragmentById(
R.id.map_fragment
) as? SupportMapFragment
mapFragment?.getMapAsync { googleMap ->
addMarkers(googleMap)
}
In der Implementierung wird zuerst die SupportMapFragment
gesucht, die Sie im vorherigen Schritt hinzugefügt haben. Dazu wird die Methode findFragmentById()
für das SupportFragmentManager
-Objekt verwendet. Sobald ein Verweis abgerufen wurde, wird der getMapAsync()
-Aufruf mit einem Lambda aufgerufen. An diese Lambda-Funktion wird das GoogleMap
-Objekt übergeben. Innerhalb dieses Lambda wird der Methodenaufruf addMarkers()
aufgerufen, der kurz definiert wird.
Bereitgestellte Klasse: PlacesReader
Im Starterprojekt ist die Klasse PlacesReader
bereits vorhanden. Diese Klasse liest eine Liste mit 49 Orten, die in einer JSON-Datei mit dem Namen places.json
gespeichert sind, und gibt sie als List<Place>
zurück. Die Orte selbst stellen eine Liste von Fahrradgeschäften in der Umgebung von San Francisco, Kalifornien, USA dar.
Wenn Sie mehr über die Implementierung dieser Klasse erfahren möchten, können Sie sie auf GitHub aufrufen oder die Klasse PlacesReader
in Android Studio öffnen.
PlacesReader
package com.google.codelabs.buildyourfirstmap.place
import android.content.Context
import com.google.codelabs.buildyourfirstmap.R
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.io.InputStream
import java.io.InputStreamReader
/**
* Reads a list of place JSON objects from the file places.json
*/
class PlacesReader(private val context: Context) {
// GSON object responsible for converting from JSON to a Place object
private val gson = Gson()
// InputStream representing places.json
private val inputStream: InputStream
get() = context.resources.openRawResource(R.raw.places)
/**
* Reads the list of place JSON objects in the file places.json
* and returns a list of Place objects
*/
fun read(): List<Place> {
val itemType = object : TypeToken<List<PlaceResponse>>() {}.type
val reader = InputStreamReader(inputStream)
return gson.fromJson<List<PlaceResponse>>(reader, itemType).map {
it.toPlace()
}
}
Orte laden
Wenn Sie die Liste der Fahrradgeschäfte laden möchten, fügen Sie in MainActivity
eine Property namens places
hinzu und definieren Sie sie so:
MainActivity.places
private val places: List<Place> by lazy {
PlacesReader(this).read()
}
Mit diesem Code wird die Methode read()
für ein PlacesReader
aufgerufen, das ein List<Place>
zurückgibt. Ein Place
hat ein Attribut namens name
, den Namen des Orts, und ein latLng
, die Koordinaten des Orts.
Ort
data class Place(
val name: String,
val latLng: LatLng,
val address: LatLng,
val rating: Float
)
Markierungen zur Karte hinzufügen
Nachdem die Liste der Orte in den Arbeitsspeicher geladen wurde, müssen sie auf der Karte dargestellt werden.
- Erstellen Sie in
MainActivity
eine Methode mit dem NamenaddMarkers()
und definieren Sie sie so:
MainActivity.addMarkers()
/**
* Adds marker representations of the places list on the provided GoogleMap object
*/
private fun addMarkers(googleMap: GoogleMap) {
places.forEach { place ->
val marker = googleMap.addMarker(
MarkerOptions()
.title(place.name)
.position(place.latLng)
)
}
}
Diese Methode durchläuft die Liste der places
und ruft dann die Methode addMarker()
für das bereitgestellte GoogleMap
-Objekt auf. Die Markierung wird durch Instanziieren eines MarkerOptions
-Objekts erstellt. So können Sie die Markierung selbst anpassen. In diesem Fall werden der Titel und die Position der Markierung angegeben, die jeweils für den Namen des Fahrradgeschäfts und seine Koordinaten stehen.
- Führen Sie die App aus und fahren Sie nach San Francisco, um die Markierungen zu sehen, die Sie gerade hinzugefügt haben.
7. 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 sehen wir uns einige davon an. Sie passen das Bild jeder Markierung sowie das Informationsfenster an, das angezeigt wird, wenn auf eine Markierung getippt wird.
Infofenster hinzufügen
Standardmäßig werden im Infofenster, das angezeigt wird, wenn Sie auf eine Markierung tippen, der Titel und das Snippet der Markierung (falls festgelegt) angezeigt. Sie können diese so anpassen, dass zusätzliche Informationen angezeigt werden, z. B. die Adresse und die Bewertung des Orts.
marker_info_contents.xml erstellen
Erstellen Sie zuerst eine neue Layoutdatei mit dem Namen marker_info_contents.xml
.
- Klicken Sie dazu in der Projektansicht in Android Studio mit der rechten Maustaste auf den Ordner
app/src/main/res/layout
und wählen Sie Neu > Layout Resource File (Layout-Ressourcendatei) aus.
- Geben Sie im Dialogfeld
marker_info_contents
in das Feld Dateiname undLinearLayout
in das FeldRoot element
ein und klicken Sie dann auf OK.
Diese Layoutdatei wird später aufgebläht, um den Inhalt des Infofensters darzustellen.
- Kopieren Sie den Inhalt des folgenden Code-Snippets, mit dem drei
TextViews
in einer vertikalenLinearLayout
-Ansichtsgruppe hinzugefügt werden, und überschreiben Sie den Standardcode in der Datei.
marker_info_contents.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:padding="8dp">
<TextView
android:id="@+id/text_view_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold"
tools:text="Title"/>
<TextView
android:id="@+id/text_view_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:textSize="16sp"
tools:text="123 Main Street"/>
<TextView
android:id="@+id/text_view_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:textSize="16sp"
tools:text="Rating: 3"/>
</LinearLayout>
InfoWindowAdapter implementieren
Nachdem Sie die Layoutdatei für das benutzerdefinierte Infofenster erstellt haben, müssen Sie als Nächstes die Schnittstelle GoogleMap.InfoWindowAdapter implementieren. Diese Schnittstelle enthält zwei Methoden: getInfoWindow()
und getInfoContents()
. Beide Methoden geben ein optionales View
-Objekt zurück. Mit der ersten Methode wird das Fenster selbst angepasst, mit der zweiten der Inhalt. In Ihrem Fall implementieren Sie beide und passen die Rückgabe von getInfoContents()
an, während Sie in getInfoWindow()
„null“ zurückgeben. Das bedeutet, dass das Standardzeitfenster verwendet werden soll.
- Erstellen Sie eine neue Kotlin-Datei namens
MarkerInfoWindowAdapter
im selben Paket wieMainActivity
. Klicken Sie dazu in der Projektansicht in Android Studio mit der rechten Maustaste auf den Ordnerapp/src/main/java/com/google/codelabs/buildyourfirstmap
und wählen Sie Neu > Kotlin-Datei/Klasse aus.
- Geben Sie im Dialogfeld
MarkerInfoWindowAdapter
ein und lassen Sie Datei markiert.
- Kopieren Sie den Inhalt des folgenden Code-Snippets in die neue Datei.
MarkerInfoWindowAdapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.model.Marker
import com.google.codelabs.buildyourfirstmap.place.Place
class MarkerInfoWindowAdapter(
private val context: Context
) : GoogleMap.InfoWindowAdapter {
override fun getInfoContents(marker: Marker?): View? {
// 1. Get tag
val place = marker?.tag as? Place ?: return null
// 2. Inflate view and set title, address, and rating
val view = LayoutInflater.from(context).inflate(
R.layout.marker_info_contents, null
)
view.findViewById<TextView>(
R.id.text_view_title
).text = place.name
view.findViewById<TextView>(
R.id.text_view_address
).text = place.address
view.findViewById<TextView>(
R.id.text_view_rating
).text = "Rating: %.2f".format(place.rating)
return view
}
override fun getInfoWindow(marker: Marker?): View? {
// Return null to indicate that the
// default window (white bubble) should be used
return null
}
}
Im Inhalt der Methode getInfoContents()
wird der bereitgestellte Marker in der Methode in den Typ Place
umgewandelt. Wenn die Umwandlung nicht möglich ist, gibt die Methode „null“ zurück. Sie haben das Attribut „tag“ für Marker
noch nicht festgelegt, aber das tun Sie im nächsten Schritt.
Als Nächstes wird das Layout marker_info_contents.xml
aufgeblasen und dann der Text im Container TextViews
auf das Tag Place
festgelegt.
MainActivity aktualisieren
Um alle bisher erstellten Komponenten zu verbinden, müssen Sie Ihrer MainActivity
-Klasse zwei Zeilen hinzufügen.
Um die benutzerdefinierten InfoWindowAdapter
- und MarkerInfoWindowAdapter
-Parameter im getMapAsync
-Methodenaufruf zu übergeben, rufen Sie zuerst die setInfoWindowAdapter()
-Methode für das GoogleMap
-Objekt auf und erstellen Sie eine neue Instanz von MarkerInfoWindowAdapter
.
- Fügen Sie dazu den folgenden Code nach dem
addMarkers()
-Methodenaufruf imgetMapAsync()
-Lambda ein.
MainActivity.onCreate()
// Set custom info window adapter
googleMap.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))
Schließlich müssen Sie jeden Ort als Tag-Eigenschaft für jede Markierung festlegen, die der Karte hinzugefügt wird.
- Ändern Sie dazu den
places.forEach{}
-Aufruf in der FunktionaddMarkers()
so:
MainActivity.addMarkers()
places.forEach { place ->
val marker = googleMap.addMarker(
MarkerOptions()
.title(place.name)
.position(place.latLng)
.icon(bicycleIcon)
)
// Set place as the tag on the marker object so it can be referenced within
// MarkerInfoWindowAdapter
marker.tag = place
}
Benutzerdefiniertes Markierungsbild hinzufügen
Das Markierungsbild anzupassen ist eine gute Möglichkeit, den Typ des Orts, den die Markierung auf Ihrer Karte darstellt, zu kommunizieren. In diesem Schritt werden Fahrräder anstelle der roten Standardmarkierungen verwendet, um die einzelnen Geschäfte auf der Karte darzustellen. Das Starterprojekt enthält das Fahrradsymbol ic_directions_bike_black_24dp.xml
in app/src/res/drawable
, das Sie verwenden.
Benutzerdefinierte Bitmap für Markierung festlegen
Nachdem Sie das Vektorsymbol für das Fahrrad haben, müssen Sie es als Symbol für jede Markierung auf der Karte festlegen. MarkerOptions
hat eine Methode icon
, die ein BitmapDescriptor
akzeptiert, mit dem Sie dies erreichen können.
Zuerst müssen Sie die gerade hinzugefügte Vektordrawable in ein BitmapDescriptor
konvertieren. Eine Datei namens BitMapHelper
, die im Starterprojekt enthalten ist, enthält eine Hilfsfunktion namens vectorToBitmap()
, die genau das tut.
BitmapHelper
package com.google.codelabs.buildyourfirstmap
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.util.Log
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.DrawableCompat
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.BitmapDescriptorFactory
object BitmapHelper {
/**
* Demonstrates converting a [Drawable] to a [BitmapDescriptor],
* for use as a marker icon. Taken from ApiDemos on GitHub:
* https://github.com/googlemaps/android-samples/blob/main/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/MarkerDemoActivity.kt
*/
fun vectorToBitmap(
context: Context,
@DrawableRes id: Int,
@ColorInt color: Int
): BitmapDescriptor {
val vectorDrawable = ResourcesCompat.getDrawable(context.resources, id, null)
if (vectorDrawable == null) {
Log.e("BitmapHelper", "Resource not found")
return BitmapDescriptorFactory.defaultMarker()
}
val bitmap = Bitmap.createBitmap(
vectorDrawable.intrinsicWidth,
vectorDrawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
vectorDrawable.setBounds(0, 0, canvas.width, canvas.height)
DrawableCompat.setTint(vectorDrawable, color)
vectorDrawable.draw(canvas)
return BitmapDescriptorFactory.fromBitmap(bitmap)
}
}
Diese Methode nimmt eine Context
, eine ID für eine zeichnungsfähige Ressource sowie eine Farbganzzahl entgegen und erstellt eine BitmapDescriptor
-Darstellung davon.
Deklarieren Sie mit der Hilfsmethode ein neues Attribut namens bicycleIcon
und geben Sie ihm die folgende Definition: MainActivity.bicycleIcon
private val bicycleIcon: BitmapDescriptor by lazy {
val color = ContextCompat.getColor(this, R.color.colorPrimary)
BitmapHelper.vectorToBitmap(this, R.drawable.ic_directions_bike_black_24dp, color)
}
Für diese Property wird die vordefinierte Farbe colorPrimary
in Ihrer App verwendet, um das Fahrradsymbol zu tönen und als BitmapDescriptor
zurückzugeben.
- Rufen Sie mit dieser Eigenschaft die Methode
icon
vonMarkerOptions
in der MethodeaddMarkers()
auf, um die Symbolanpassung abzuschließen. Die Markierungseigenschaft sollte dann so aussehen:
MainActivity.addMarkers()
val marker = googleMap.addMarker(
MarkerOptions()
.title(place.name)
.position(place.latLng)
.icon(bicycleIcon)
)
- Führen Sie die App aus, um die aktualisierten Markierungen zu sehen.
8. Markierungs-Clustering
Je nachdem, wie weit Sie in die Karte hineingezoomt haben, haben Sie vielleicht bemerkt, dass sich die von Ihnen hinzugefügten Markierungen überlappen. Überlappende Markierungen sind sehr schwer zu bedienen und verursachen viel Rauschen, was die Nutzerfreundlichkeit Ihrer App beeinträchtigt.
Um die Nutzerfreundlichkeit zu verbessern, empfiehlt es sich, Markierungscluster zu implementieren, wenn Sie ein großes Dataset haben, das eng geclustert ist. Beim Ein- und Auszoomen der Karte werden Markierungen, die sich in unmittelbarer Nähe befinden, so gruppiert:
Dazu benötigen Sie die Maps SDK for Android-Dienstprogrammbibliothek.
Maps SDK for Android-Dienstprogrammbibliothek
Die Maps SDK for Android-Dienstprogrammbibliothek wurde entwickelt, um die Funktionalität des Maps SDK for Android zu erweitern. Es bietet erweiterte Funktionen wie Marker-Clustering, Heatmaps, KML- und GeoJSON-Unterstützung, Polyline-Codierung und -Decodierung sowie eine Reihe von Hilfsfunktionen für die sphärische Geometrie.
build.gradle-Datei aktualisieren
Da die Dienstprogrammbibliothek separat vom Maps SDK for Android verpackt ist, müssen Sie Ihrer build.gradle
-Datei eine zusätzliche Abhängigkeit hinzufügen.
- Aktualisieren Sie den Abschnitt
dependencies
Ihrer Dateiapp/build.gradle
.
build.gradle
implementation 'com.google.maps.android:android-maps-utils:1.1.0'
- Nachdem Sie diese Zeile hinzugefügt haben, müssen Sie das Projekt synchronisieren, um die neuen Abhängigkeiten abzurufen.
Clustering implementieren
So implementieren Sie Clustering in Ihrer App:
- Implementieren Sie die
ClusterItem
-Schnittstelle. - Leiten Sie die Klasse
DefaultClusterRenderer
ab. - Erstellen Sie ein
ClusterManager
und fügen Sie Elemente hinzu.
ClusterItem-Schnittstelle implementieren
Alle Objekte, die eine clusterfähige Markierung auf der Karte darstellen, müssen die ClusterItem
-Schnittstelle implementieren. In Ihrem Fall bedeutet das, dass das Place
-Modell ClusterItem
entsprechen muss. Öffnen Sie die Datei Place.kt
und nehmen Sie die folgenden Änderungen vor:
Ort
data class Place(
val name: String,
val latLng: LatLng,
val address: String,
val rating: Float
) : ClusterItem {
override fun getPosition(): LatLng =
latLng
override fun getTitle(): String =
name
override fun getSnippet(): String =
address
}
Die ClusterItem-Klasse definiert diese drei Methoden:
getPosition()
, das für dieLatLng
des Orts steht.getTitle()
, das für den Namen des Orts stehtgetSnippet()
, das für die Adresse des Orts steht.
Unterklasse der DefaultClusterRenderer-Klasse erstellen
Die Klasse, die für die Implementierung des Clusterings zuständig ist, ClusterManager
, verwendet intern eine ClusterRenderer
-Klasse, um die Cluster zu erstellen, während Sie die Karte schwenken und zoomen. Standardmäßig ist der Standard-Renderer DefaultClusterRenderer
enthalten, der ClusterRenderer
implementiert. Für einfache Fälle sollte das ausreichen. Da Markierungen in Ihrem Fall angepasst werden müssen, müssen Sie diese Klasse erweitern und die Anpassungen dort hinzufügen.
Erstellen Sie die Kotlin-Datei PlaceRenderer.kt
im Paket com.google.codelabs.buildyourfirstmap.place
und definieren Sie sie so:
PlaceRenderer
package com.google.codelabs.buildyourfirstmap.place
import android.content.Context
import androidx.core.content.ContextCompat
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.Marker
import com.google.android.gms.maps.model.MarkerOptions
import com.google.codelabs.buildyourfirstmap.BitmapHelper
import com.google.codelabs.buildyourfirstmap.R
import com.google.maps.android.clustering.ClusterManager
import com.google.maps.android.clustering.view.DefaultClusterRenderer
/**
* A custom cluster renderer for Place objects.
*/
class PlaceRenderer(
private val context: Context,
map: GoogleMap,
clusterManager: ClusterManager<Place>
) : DefaultClusterRenderer<Place>(context, map, clusterManager) {
/**
* The icon to use for each cluster item
*/
private val bicycleIcon: BitmapDescriptor by lazy {
val color = ContextCompat.getColor(context,
R.color.colorPrimary
)
BitmapHelper.vectorToBitmap(
context,
R.drawable.ic_directions_bike_black_24dp,
color
)
}
/**
* Method called before the cluster item (the marker) is rendered.
* This is where marker options should be set.
*/
override fun onBeforeClusterItemRendered(
item: Place,
markerOptions: MarkerOptions
) {
markerOptions.title(item.name)
.position(item.latLng)
.icon(bicycleIcon)
}
/**
* Method called right after the cluster item (the marker) is rendered.
* This is where properties for the Marker object should be set.
*/
override fun onClusterItemRendered(clusterItem: Place, marker: Marker) {
marker.tag = clusterItem
}
}
In dieser Klasse werden die folgenden beiden Funktionen überschrieben:
onBeforeClusterItemRendered()
wird aufgerufen, bevor der Cluster auf der Karte gerendert wird. Hier können Sie Anpassungen überMarkerOptions
vornehmen. In diesem Fall werden Titel, Position und Symbol der Markierung festgelegt.onClusterItemRenderer()
, die direkt nach dem Rendern der Markierung auf der Karte aufgerufen wird. Hier können Sie auf das erstellteMarker
-Objekt zugreifen. In diesem Fall wird damit die Tag-Eigenschaft der Markierung festgelegt.
ClusterManager erstellen und Elemente hinzufügen
Damit das Clustering funktioniert, müssen Sie MainActivity
so ändern, dass eine ClusterManager
instanziiert und die erforderlichen Abhängigkeiten bereitgestellt werden. ClusterManager
kümmert sich intern um das Hinzufügen der Markierungen (der ClusterItem
-Objekte). Anstatt Markierungen direkt auf der Karte hinzuzufügen, wird diese Aufgabe also an ClusterManager
delegiert. Außerdem wird durch ClusterManager
intern auch setInfoWindowAdapter()
aufgerufen. Das Festlegen eines benutzerdefinierten Infofensters muss also für das MarkerManager.Collection
-Objekt von ClusterManger
erfolgen.
- Ändern Sie zuerst den Inhalt des Lambda-Ausdrucks im
getMapAsync()
-Aufruf inMainActivity.onCreate()
. Kommentieren Sie den Aufruf vonaddMarkers()
undsetInfoWindowAdapter()
aus und rufen Sie stattdessen eine Methode namensaddClusteredMarkers()
auf, die Sie als Nächstes definieren.
MainActivity.onCreate()
mapFragment?.getMapAsync { googleMap ->
//addMarkers(googleMap)
addClusteredMarkers(googleMap)
// Set custom info window adapter.
// googleMap.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))
}
- Als Nächstes definieren Sie
addClusteredMarkers()
inMainActivity
.
MainActivity.addClusteredMarkers()
/**
* Adds markers to the map with clustering support.
*/
private fun addClusteredMarkers(googleMap: GoogleMap) {
// Create the ClusterManager class and set the custom renderer.
val clusterManager = ClusterManager<Place>(this, googleMap)
clusterManager.renderer =
PlaceRenderer(
this,
googleMap,
clusterManager
)
// Set custom info window adapter
clusterManager.markerCollection.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))
// Add the places to the ClusterManager.
clusterManager.addItems(places)
clusterManager.cluster()
// Set ClusterManager as the OnCameraIdleListener so that it
// can re-cluster when zooming in and out.
googleMap.setOnCameraIdleListener {
clusterManager.onCameraIdle()
}
}
Mit dieser Methode wird ein ClusterManager
instanziiert, der benutzerdefinierte Renderer PlacesRenderer
übergeben, alle Orte hinzugefügt und die Methode cluster()
aufgerufen. Da ClusterManager
die setInfoWindowAdapter()
-Methode für das Kartenobjekt verwendet, muss das benutzerdefinierte Infofenster für das ClusterManager.markerCollection
-Objekt festgelegt werden. Da sich das Clustering ändern soll, wenn der Nutzer die Karte schwenkt und zoomt, wird OnCameraIdleListener
für googleMap
bereitgestellt. Wenn die Kamera inaktiv wird, wird clusterManager.onCameraIdle()
aufgerufen.
- Führen Sie die App aus, um die neuen gruppierten Geschäfte zu sehen.
9. 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 Polylinien und Polygone verwenden. Wenn Sie ein Bild auf der Erdoberfläche fixieren möchten, können Sie Boden-Overlays verwenden.
In dieser Aufgabe lernen Sie, wie Sie Formen, insbesondere einen Kreis, um eine Markierung zeichnen, wenn darauf getippt wird.
Klick-Listener hinzufügen
Normalerweise fügen Sie einer Markierung einen Klick-Listener hinzu, indem Sie über setOnMarkerClickListener()
einen Klick-Listener direkt für das GoogleMap
-Objekt übergeben. Da Sie jedoch Clustering verwenden, muss der Klick-Listener stattdessen für ClusterManager
bereitgestellt werden.
- Fügen Sie in der Methode
addClusteredMarkers()
inMainActivity
die folgende Zeile direkt nach dem Aufruf voncluster()
hinzu.
MainActivity.addClusteredMarkers()
// Show polygon
clusterManager.setOnClusterItemClickListener { item ->
addCircle(googleMap, item)
return@setOnClusterItemClickListener false
}
Mit dieser Methode wird ein Listener hinzugefügt und die Methode addCircle()
aufgerufen, die Sie als Nächstes definieren. Schließlich wird false
von dieser Methode zurückgegeben, um anzugeben, dass dieses Ereignis nicht verarbeitet wurde.
- Als Nächstes müssen Sie das Attribut
circle
und die MethodeaddCircle()
inMainActivity
definieren.
MainActivity.addCircle()
private var circle: Circle? = null
/**
* Adds a [Circle] around the provided [item]
*/
private fun addCircle(googleMap: GoogleMap, item: Place) {
circle?.remove()
circle = googleMap.addCircle(
CircleOptions()
.center(item.latLng)
.radius(1000.0)
.fillColor(ContextCompat.getColor(this, R.color.colorPrimaryTranslucent))
.strokeColor(ContextCompat.getColor(this, R.color.colorPrimary))
)
}
Das Attribut circle
ist so festgelegt, dass bei jedem Tippen auf eine neue Markierung der vorherige Kreis entfernt und ein neuer Kreis hinzugefügt wird. Die API zum Hinzufügen eines Kreises ähnelt der zum Hinzufügen einer Markierung.
- Führen Sie die App jetzt aus, um die Änderungen zu sehen.
10. Kamerasteuerung
Als letzte Aufgabe sehen Sie sich einige Kamerasteuerelemente an, damit Sie den Blick auf eine bestimmte Region richten können.
Kamera und Ansicht
Wenn Sie die App ausführen, wird auf der Kamera der Kontinent Afrika angezeigt. Sie müssen dann mühsam schwenken und zoomen, um die von Ihnen hinzugefügten Markierungen in San Francisco zu finden. Das kann eine unterhaltsame Möglichkeit sein, die Welt zu erkunden, ist aber nicht hilfreich, wenn Sie die Markierungen sofort anzeigen möchten.
Dazu können Sie die Position der Kamera programmatisch so festlegen, dass die Ansicht dort zentriert wird, wo Sie sie haben möchten.
- Fügen Sie dem
getMapAsync()
-Aufruf den folgenden Code hinzu, um die Kameraansicht so anzupassen, dass sie beim Start der App auf San Francisco initialisiert wird.
MainActivity.onCreate()
mapFragment?.getMapAsync { googleMap ->
// Ensure all places are visible in the map.
googleMap.setOnMapLoadedCallback {
val bounds = LatLngBounds.builder()
places.forEach { bounds.include(it.latLng) }
googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), 20))
}
}
Zuerst wird setOnMapLoadedCallback()
aufgerufen, damit die Kamera erst aktualisiert wird, nachdem die Karte geladen wurde. Dieser Schritt ist erforderlich, da die Karteneigenschaften wie Dimensionen berechnet werden müssen, bevor ein Kamera-Update-Aufruf erfolgt.
Im Lambda wird ein neues LatLngBounds
-Objekt erstellt, das einen rechteckigen Bereich auf der Karte definiert. Sie wird inkrementell erstellt, indem alle LatLng
-Werte für Orte einbezogen werden, damit alle Orte innerhalb der Grenzen liegen. Sobald dieses Objekt erstellt wurde, wird die moveCamera()
-Methode für GoogleMap
aufgerufen und ein CameraUpdate
wird über CameraUpdateFactory.newLatLngBounds(bounds.build(), 20)
bereitgestellt.
- Führen Sie die App aus. Die Kamera wird jetzt in San Francisco initialisiert.
Kameraänderungen beobachten
Sie können nicht nur die Kameraposition ändern, sondern auch auf Kamera-Updates reagieren, wenn sich der Nutzer auf der Karte bewegt. Das kann nützlich sein, wenn Sie die Benutzeroberfläche anpassen möchten, während sich die Kamera bewegt.
Sie ändern den Code, damit die Markierungen durchscheinend werden, wenn die Kamera bewegt wird.
- Fügen Sie in der Methode
addClusteredMarkers()
die folgenden Zeilen am Ende der Methode hinzu:
MainActivity.addClusteredMarkers()
// When the camera starts moving, change the alpha value of the marker to translucent.
googleMap.setOnCameraMoveStartedListener {
clusterManager.markerCollection.markers.forEach { it.alpha = 0.3f }
clusterManager.clusterMarkerCollection.markers.forEach { it.alpha = 0.3f }
}
Dadurch wird ein OnCameraMoveStartedListener
hinzugefügt. Wenn sich die Kamera bewegt, werden die Alphawerte aller Markierungen (sowohl Cluster als auch einzelne Markierungen) in 0.3f
geändert, sodass die Markierungen durchscheinend dargestellt werden.
- Wenn Sie die durchscheinenden Markierungen wieder in undurchsichtige Markierungen ändern möchten, wenn die Kamera stoppt, ändern Sie den Inhalt von
setOnCameraIdleListener
in der MethodeaddClusteredMarkers()
in Folgendes:
MainActivity.addClusteredMarkers()
googleMap.setOnCameraIdleListener {
// When the camera stops moving, change the alpha value back to opaque.
clusterManager.markerCollection.markers.forEach { it.alpha = 1.0f }
clusterManager.clusterMarkerCollection.markers.forEach { it.alpha = 1.0f }
// Call clusterManager.onCameraIdle() when the camera stops moving so that reclustering
// can be performed when the camera stops moving.
clusterManager.onCameraIdle()
}
- Führen Sie die App aus, um die Ergebnisse zu sehen.
11. Maps KTX
Für Kotlin-Apps, die ein oder mehrere Google Maps Platform Android SDKs verwenden, sind Kotlin-Erweiterungs- oder KTX-Bibliotheken verfügbar, mit denen Sie Kotlin-Sprachfunktionen wie Koroutinen, Erweiterungseigenschaften/-funktionen usw. nutzen können. Für jedes Google Maps SDK gibt es eine entsprechende KTX-Bibliothek, wie unten dargestellt:
In dieser Aufgabe verwenden Sie die Maps KTX- und Maps Utils KTX-Bibliotheken in Ihrer App und überarbeiten die Implementierungen der vorherigen Aufgaben, damit Sie Kotlin-spezifische Sprachfunktionen in Ihrer App verwenden können.
- KTX-Abhängigkeiten in die Datei „build.gradle“ auf App-Ebene einfügen
Da in der App sowohl das Maps SDK for Android als auch die Maps SDK for Android-Dienstprogrammbibliothek verwendet werden, müssen Sie die entsprechenden KTX-Bibliotheken für diese Bibliotheken einbinden. In dieser Aufgabe verwenden Sie auch eine Funktion aus der AndroidX Lifecycle KTX-Bibliothek. Fügen Sie diese Abhängigkeit daher auch in die build.gradle
-Datei auf App-Ebene ein.
build.gradle
dependencies {
// ...
// Maps SDK for Android KTX Library
implementation 'com.google.maps.android:maps-ktx:3.0.0'
// Maps SDK for Android Utility Library KTX Library
implementation 'com.google.maps.android:maps-utils-ktx:3.0.0'
// Lifecycle Runtime KTX Library
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
}
- Erweiterungsfunktionen „GoogleMap.addMarker()“ und „GoogleMap.addCircle()“ verwenden
Die Maps KTX-Bibliothek bietet eine API-Alternative im DSL-Stil für die GoogleMap.addMarker(MarkerOptions)
und GoogleMap.addCircle(CircleOptions)
, die in den vorherigen Schritten verwendet wurden. Für die Verwendung der oben genannten APIs ist die Erstellung einer Klasse mit Optionen für eine Markierung oder einen Kreis erforderlich. Bei den KTX-Alternativen können Sie die Markierungs- oder Kreisoptionen in der bereitgestellten Lambda-Funktion festlegen.
Wenn Sie diese APIs verwenden möchten, aktualisieren Sie die Methoden MainActivity.addMarkers(GoogleMap)
und MainActivity.addCircle(GoogleMap)
:
MainActivity.addMarkers(GoogleMap)
/**
* Adds markers to the map. These markers won't be clustered.
*/
private fun addMarkers(googleMap: GoogleMap) {
places.forEach { place ->
val marker = googleMap.addMarker {
title(place.name)
position(place.latLng)
icon(bicycleIcon)
}
// Set place as the tag on the marker object so it can be referenced within
// MarkerInfoWindowAdapter
marker.tag = place
}
}
MainActivity.addCircle(GoogleMap)
/**
* Adds a [Circle] around the provided [item]
*/
private fun addCircle(googleMap: GoogleMap, item: Place) {
circle?.remove()
circle = googleMap.addCircle {
center(item.latLng)
radius(1000.0)
fillColor(ContextCompat.getColor(this@MainActivity, R.color.colorPrimaryTranslucent))
strokeColor(ContextCompat.getColor(this@MainActivity, R.color.colorPrimary))
}
}
Die oben genannten Methoden auf diese Weise neu zu schreiben, ist viel prägnanter. Das ist dank des Funktionsliterals mit Empfänger von Kotlin möglich.
- Die suspend-Funktionen SupportMapFragment.awaitMap() und GoogleMap.awaitMapLoad() verwenden
Die Maps KTX-Bibliothek bietet auch Erweiterungen für suspend-Funktionen, die in Koroutinen verwendet werden können. Konkret gibt es Alternativen für die suspend-Funktionen für SupportMapFragment.getMapAsync(OnMapReadyCallback)
und GoogleMap.setOnMapLoadedCallback(OnMapLoadedCallback)
. Wenn Sie diese alternativen APIs verwenden, müssen Sie keine Callbacks übergeben. Stattdessen können Sie die Antwort dieser Methoden seriell und synchron empfangen.
Da es sich bei diesen Methoden um suspend-Funktionen handelt, muss ihre Verwendung innerhalb einer Coroutine erfolgen. Die Lifecycle Runtime KTX-Bibliothek bietet eine Erweiterung, mit der lebenszyklusbezogene Coroutine-Scopes bereitgestellt werden können, sodass Coroutines beim entsprechenden Lebenszyklusereignis ausgeführt und beendet werden.
Kombinieren Sie diese Konzepte und aktualisieren Sie die Methode MainActivity.onCreate(Bundle)
:
MainActivity.onCreate(Bundle)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mapFragment =
supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment
lifecycleScope.launchWhenCreated {
// Get map
val googleMap = mapFragment.awaitMap()
// Wait for map to finish loading
googleMap.awaitMapLoad()
// Ensure all places are visible in the map
val bounds = LatLngBounds.builder()
places.forEach { bounds.include(it.latLng) }
googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), 20))
addClusteredMarkers(googleMap)
}
}
Der lifecycleScope.launchWhenCreated
-Coroutine-Scope führt den Block aus, wenn sich die Aktivität mindestens im Status „created“ befindet. Die Aufrufe zum Abrufen des GoogleMap
-Objekts und zum Warten auf das vollständige Laden der Karte wurden durch SupportMapFragment.awaitMap()
bzw. GoogleMap.awaitMapLoad()
ersetzt. Durch das Refactoring von Code mit diesen suspend-Funktionen können Sie den entsprechenden Callback-basierten Code sequenziell schreiben.
- Erstellen Sie die App mit den refaktorierten Änderungen neu.
12. 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
- Places SDK for Android: Nutzen Sie die umfangreichen Ortsdaten, um Unternehmen in Ihrer Nähe zu finden.
- android-maps-ktx: Eine Open-Source-Bibliothek, mit der Sie das Maps SDK for Android und die Maps SDK for Android-Dienstprogrammbibliothek auf Kotlin-freundliche Weise einbinden können.
- android-place-ktx: Eine Open-Source-Bibliothek, mit der Sie das Places SDK for Android auf Kotlin-freundliche Weise einbinden können.
- android-samples: Beispielcode auf GitHub, der alle in diesem Codelab behandelten Funktionen und mehr veranschaulicht.
- Weitere Kotlin-Codelabs für die Entwicklung von Android-Apps mit der Google Maps Platform