1. Vorbereitung
In diesem Codelab lernen Sie, wie Sie das Maps SDK for Android in Ihre App einbinden und ihre wichtigsten Funktionen verwenden. Dazu erstellen Sie eine App, die eine Karte mit Fahrradgeschäften in San Francisco, USA, zeigt.
Vorbereitung
- Grundkenntnisse in der Entwicklung von Kotlin und Android
Aufgaben
- Maps SDK for Android aktivieren und Google Maps zu einer Android-App hinzufügen
- Markierungen hinzufügen, anpassen und gruppieren
- Polylinien und Polygone auf der Karte zeichnen
- Blickwinkel der Kamera programmatisch verwalten
Voraussetzungen
- Maps SDK for Android
- Ein Google-Konto mit aktivierter Abrechnung
- Android Studio 2020.3.1 oder höher
- In Android Studio installierte Google Play-Dienste
- 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. Eine Anleitung zur Installation finden Sie unter Apps im Android Emulator ausführen.
2. Einrichten
Für den folgenden Aktivierungsschritt müssen Sie das Maps SDK for Android aktivieren.
Google Maps Platform einrichten
Wenn Sie noch kein Google Cloud Platform-Konto und kein Projekt mit aktivierter Abrechnung haben, lesen Sie den Leitfaden Erste Schritte mit der Google Maps Platform, um ein Rechnungskonto und ein Projekt zu erstellen.
- Klicken Sie in der Cloud Console auf das Drop-down-Menü für Projekte und wählen Sie das Projekt aus, das Sie für dieses Codelab verwenden möchten.
- Aktivieren Sie im Google Cloud Marketplace die für dieses Codelab erforderlichen Google Maps Platform APIs und SDKs. Folgen Sie dazu der Anleitung in diesem Video oder dieser Dokumentation.
- Generieren Sie in der Cloud Console auf der Seite Anmeldedaten einen API-Schlüssel. Folgen Sie der Anleitung in diesem Video 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, erhalten Sie hier einen Startcode, den Sie bei diesem Codelab nutzen können. Sie können gerne direkt zur Lösung wechseln. Wenn Sie den Schritten aber selbst folgen möchten, lesen Sie einfach weiter.
- Wenn Sie
git
installiert haben, klonen Sie das Repository.
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.
- Wenn du den Code abgerufen hast, öffne das Projekt im Verzeichnis
starter
in Android Studio.
4. Google Maps hinzufügen
In diesem Abschnitt fügen Sie Google Maps hinzu, damit es beim Start 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 der Maps SDK for Android Ihren Schlüssel mit Ihrer App verknüpfen kann.
- Öffnen Sie dazu die Datei mit dem Namen
local.properties
im Stammverzeichnis Ihres Projekts. Das ist die Ebene, in der sichgradle.properties
undsettings.gradle
befinden. - Definieren Sie in dieser Datei einen neuen Schlüssel
GOOGLE_MAPS_API_KEY
, wobei der Wert der von Ihnen erstellte API-Schlüssel ist.
lokale 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 Informationen gilt und nach Möglichkeit nicht in der Versionskontrolle eingecheckt werden sollte.
- Um Ihre API verfügbar zu machen, damit sie in Ihrer App verwendet werden kann, müssen Sie das Secrets Gradle Plugin for Android-Plug-in in Ihrer App
build.gradle
-Datei im Verzeichnisapp/
einfügen und die folgende Zeile implugins
-Block hinzufügen:
build.gradle-Datei auf App-Ebene
plugins {
// ...
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
}
Außerdem müssen Sie die build.gradle
-Datei auf Projektebene so anpassen, dass sie den folgenden Kurspfad enthält:
build.gradle-Datei auf Projektebene
buildscript {
dependencies {
// ...
classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:1.3.0"
}
}
Mit diesem Plug-in werden Schlüssel, die Sie in Ihrer local.properties
-Datei definiert haben, bei der Erstellung als Build-Variablen in der Android-Manifestdatei und als Variablen in der von Gradle generierten BuildConfig
-Klasse verfügbar gemacht. Mit diesem Plug-in wird der Boilerplate-Code entfernt, der ansonsten zum Lesen von Properties aus local.properties
erforderlich ist, damit der Zugriff auf Ihre gesamte App möglich ist.
Google Maps-Abhängigkeit hinzufügen
- Du kannst jetzt in der App auf deinen API-Schlüssel zugreifen. Füge als Nächstes der Maps SDK for Android-Abhängigkeit der
build.gradle
-Datei deiner App hinzu.
Diese Abhängigkeit wurde im Starter-Projekt, das mit diesem Codelab geliefert wird, bereits hinzugefügt.
build.gradle
dependencies {
// Dependency to include Maps SDK for Android
implementation 'com.google.android.gms:play-services-maps:17.0.0'
}
- Als Nächstes fügen Sie in
AndroidManifest.xml
ein neuesmeta-data
-Tag hinzu, damit der API-Schlüssel übergeben wird, den Sie in einem vorherigen Schritt erstellt haben. Dazu öffnest du diese Datei in Android Studio und fügst das folgendemeta-data
-Tag in dasapplication
-Objekt in deineAndroidManifest.xml
-Datei 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}" />
- Erstelle als Nächstes eine neue Layoutdatei namens
activity_main.xml
im Verzeichnisapp/src/main/res/layout/
und definiere 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 eine einzelne FrameLayout
mit einem SupportMapFragment
. Dieses Fragment enthält das zugrunde liegende GoogleMaps
-Objekt, das Sie in späteren Schritten verwenden.
- Ergänzen Sie schließlich die Klasse
MainActivity
inapp/src/main/java/com/google/codelabs/buildyourfirstmap
, indem Sie den folgenden Code hinzufügen, um die MethodeonCreate
zu überschreiben, damit Sie deren Inhalt mit dem neu erstellten Layout festlegen können.
Hauptaktivität
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
- Jetzt kannst du die App ausführen. Du solltest jetzt den Kartenaufruf auf deinem Gerät sehen.
5. Cloudbasierter Kartenstil (optional)
Sie können den Stil Ihrer Karte mit cloudbasierten Kartenstilen anpassen.
Karten-ID erstellen
Wenn Sie noch keine Karten-ID mit einem zugehörigen Kartenstil erstellt haben, finden Sie im Leitfaden zu Karten-IDs folgende Schritte:
- Erstellen Sie eine Karten-ID.
- Kartenkarte mit einem Kartenstil verknüpfen
Karten-ID zur App hinzufügen
Bearbeite die Datei activity_main.xml
und übergib die Karten-ID im map:mapId
-Attribut von SupportMapFragment
, um die von dir erstellte Karten-ID zu verwenden.
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" />
Danach kannst du die App ausführen, um deine Karte im gewünschten Stil zu sehen.
6. Markierungen hinzufügen
In dieser Aufgabe fügen Sie der Karte POIs hinzu, die Sie auf der Karte hervorheben möchten. Zuerst rufen Sie eine Liste von Orten ab, die im Starter-Projekt für Sie angegeben wurden, und fügen diese dann der Karte hinzu. In diesem Beispiel sind dies Fahrradgeschäfte.
Verweis auf GoogleMap abrufen
Zuerst musst du einen Verweis auf das Objekt GoogleMap
abrufen, damit du die zugehörigen Methoden verwenden kannst. Fügen Sie dazu direkt nach dem Aufruf von setContentView()
den folgenden Code in die Methode MainActivity.onCreate()
ein:
MainActivity.onCreate()
val mapFragment = supportFragmentManager.findFragmentById(
R.id.map_fragment
) as? SupportMapFragment
mapFragment?.getMapAsync { googleMap ->
addMarkers(googleMap)
}
Die Implementierung findet zuerst die SupportMapFragment
, die Sie im vorherigen Schritt hinzugefügt haben, indem Sie die Methode findFragmentById()
für das SupportFragmentManager
-Objekt verwenden. Sobald eine Referenz erfasst wurde, wird der getMapAsync()
-Aufruf aufgerufen und dann ein Lambda übergeben. An diesem Lambda-Objekt wird das GoogleMap
-Objekt übergeben. In diesem Lambda wird der Methode addMarkers()
aufgerufen, der bald definiert wird.
Bereitgestellte Klasse: PlacesReader
Im Startprojekt wurde der Kurs PlacesReader
für Sie bereitgestellt. Diese Klasse liest eine Liste von 49 Orten, die in einer JSON-Datei mit dem Namen places.json
gespeichert sind, und gibt diese als List<Place>
zurück. Die Orte stellen eine Liste von Fahrradgeschäften in der Nähe von München, Kalifornien, USA dar.
Wenn du dir für die Implementierung dieses Kurses interessierst, kannst du auf GitHub darauf zugreifen oder die PlacesReader
-Klasse 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
Um die Liste der Fahrradgeschäfte zu laden, fügen Sie in MainActivity
eine Unterkunft mit dem Namen places
hinzu und definieren Sie sie so:
MainActivity.places
private val places: List<Place> by lazy {
PlacesReader(this).read()
}
Dieser Code ruft die Methode read()
für PlacesReader
auf, die ein List<Place>
zurückgibt. Ein Place
hat eine Eigenschaft namens name
, den Namen des Orts und eine latLng
– die Koordinaten, in der sich der Ort befindet.
Ort
data class Place(
val name: String,
val latLng: LatLng,
val address: LatLng,
val rating: Float
)
Markierungen zur Karte hinzufügen
Nachdem Sie die Liste mit Orten geladen haben, müssen Sie sie auf der Karte darstellen.
- Erstellen Sie in
MainActivity
die MethodeaddMarkers()
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 von places
, gefolgt von der Methode addMarker()
für das bereitgestellte Objekt GoogleMap
. Die Markierung wird durch Instanziieren eines MarkerOptions
-Objekts erstellt. Damit können Sie die Markierung selbst anpassen. In diesem Fall werden der Titel und die Position der Markierung auf der Seite des Fahrradladens bereitgestellt.
- Öffne die San Francisco App, um die soeben hinzugefügten Markierungen zu sehen.
7. Markierungen anpassen
Es gibt mehrere Anpassungsoptionen für Markierungen, die Sie gerade hinzugefügt haben. So können sie sich von anderen abheben und Nutzern hilfreiche Informationen zur Verfügung stellen. In dieser Aufgabe stellen Sie einige davon vor. Passen Sie dazu das Bild der einzelnen Markierungen sowie das Informationsfenster an, das beim Tippen darauf angezeigt wird.
Infofenster hinzufügen
Standardmäßig wird das Infofenster, wenn Sie auf eine Markierung tippen, den Titel und das Snippet anzeigen (sofern festgelegt). Sie können die ID so anpassen, dass zusätzliche Informationen wie die Adresse und Bewertung angezeigt werden können.
Markierung_info_contents.xml erstellen
Erstelle zuerst eine neue Layoutdatei namens marker_info_contents.xml
.
- Klicken Sie dazu in Android Studio in der Projektansicht mit der rechten Maustaste auf den Ordner
app/src/main/res/layout
und wählen Sie New > Layout Resource aus.
- Geben Sie im Dialogfeld
marker_info_contents
in das Feld File name (Dateiname) undLinearLayout
in das FeldRoot element
ein. Klicken Sie dann auf OK.
Diese Layoutdatei wird später erhöht, um den Inhalt des Infofensters zu darstellen.
- Kopieren Sie den Inhalt im folgenden Code-Snippet, das drei
TextViews
in einer vertikalenLinearLayout
-Ansichtsgruppe hinzufügt und den Standardcode in der Datei überschreibt.
markierung_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>
Implementierung eines InfoWindowAdapters erstellen
Nachdem Sie die Layoutdatei für das benutzerdefinierte Infofenster erstellt haben, implementieren Sie als Nächstes die Schnittstelle GoogleMap.InfoWindowAdapter. Diese Schnittstelle enthält die beiden Methoden getInfoWindow()
und getInfoContents()
. Bei beiden Methoden wird ein optionales View
-Objekt zurückgegeben, bei dem das erste Objekt zur Anpassung des Fensters selbst verwendet wird, während das zweite Objekt verwendet wird, um den Inhalt anzupassen. In Ihrem Fall implementieren Sie beide und passen die Rückgabe von getInfoContents()
an, während NULL in getInfoWindow()
zurückgegeben wird. Das bedeutet, dass das Standardfenster verwendet werden sollte.
- Erstellen Sie eine neue Kotlin-Datei namens
MarkerInfoWindowAdapter
im selben Paket wieMainActivity
. Klicken Sie dazu in Android Studio in der Projektansicht 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.
- Nachdem Sie die Datei erstellt haben, 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 getInfoContents()
-Methode wird der bereitgestellte Marker in die Methode Place
umgewandelt. Wenn das Streamen nicht möglich ist, gibt die Methode null zurück. Du hast die Tag-Property von Marker
noch nicht festgelegt, aber das machst du im nächsten Schritt.
Als Nächstes wird das Layout marker_info_contents.xml
aufgebläht, wobei der Text für TextViews
auf das Place
-Tag festgelegt wird.
Hauptaktivität aktualisieren
Damit alle Komponenten, die Sie bisher erstellt haben, geglättet werden können, müssen Sie Ihrem MainActivity
-Kurs zwei Zeilen hinzufügen.
Um die benutzerdefinierte InfoWindowAdapter
MarkerInfoWindowAdapter
im Methodenaufruf getMapAsync
zu übergeben, starten Sie die Methode setInfoWindowAdapter()
im Objekt GoogleMap
und erstellen eine neue Instanz von MarkerInfoWindowAdapter
.
- Füge dazu den folgenden Code nach dem
addMarkers()
-Methodeaufruf imgetMapAsync()
-Lambda hinzu.
MainActivity.onCreate()
// Set custom info window adapter
googleMap.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))
Schließlich musst du jeden Ort als Tag-Property für jede Markierung festlegen, die der Karte hinzugefügt wird.
- Ändern Sie dazu den Aufruf von
places.forEach{}
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 Bild für die Markierung hinzufügen
Das Anpassen des Markierungsbilds ist eine von Möglichkeiten, um die Art des Orts anzugeben, den die Markierung auf der Karte darstellt. In diesem Schritt werden Fahrräder anstelle der standardmäßigen roten Markierungen angezeigt, um die einzelnen Geschäfte auf der Karte darzustellen. Das Starter-Projekt enthält das Fahrradsymbol ic_directions_bike_black_24dp.xml
in app/src/res/drawable
, das Sie verwenden.
Benutzerdefinierte Bitmap für Markierung festlegen
Wenn Sie ein Vektor-Zeichnen eines Fahrradsymbols zur Hand haben, legen Sie als Nächstes das Zeichen in Form der Markierungen auf der Karte fest. MarkerOptions
hat die Methode icon
, für die ein BitmapDescriptor
verwendet wird, mit dem Sie das erreichen.
Zuerst musst du die Vektordatei, die du gerade hinzugefügt hast, in einen BitmapDescriptor
konvertieren. Eine Datei namens BitMapHelper
im Startprojekt enthält eine Hilfsfunktion namens vectorToBitmap()
, die genau das tut.
BitmapAssistent
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)
}
}
Bei dieser Methode wird ein Context
-Objekt, eine Ressourcen-ID beim Zeichnen, sowie eine Farb Ganzzahl verwendet, und eine BitmapDescriptor
-Darstellung davon erstellt.
Deklarieren Sie mit der Hilfsmethode eine neue Eigenschaft namens bicycleIcon
und geben Sie ihr 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 deiner App verwendet. Damit wird die Fahrradfarbe gefärbt und als BitmapDescriptor
zurückgegeben.
- Rufe dazu mit dieser Property die
icon
-Methode vonMarkerOptions
in deraddMarkers()
-Methode auf. Danach sollte die Markierungs-Property folgendermaßen aussehen:
MainActivity.addMarkers()
val marker = googleMap.addMarker(
MarkerOptions()
.title(place.name)
.position(place.latLng)
.icon(bicycleIcon)
)
- Führe die App aus, um die aktualisierten Markierungen zu sehen.
8. Clustermarkierungen
Je nachdem, wie weit Sie auf die Karte heranzoomen, ist Ihnen möglicherweise aufgefallen, dass sich die hinzugefügten Markierungen überschneiden. Sich überschneidende Markierungen sind sehr schwer zu interagieren und verursachen viel Lärm, was die Nutzerfreundlichkeit deiner App beeinträchtigt.
Wenn Sie ein großes Dataset haben, das sich eng beieinander befindet, sollten Sie zur Optimierung die Nutzerfreundlichkeit der Markierungs-Clustering verbessern. Beim Clustering werden beim Heran- und Herauszoomen Markierungen in naher Nähe gruppiert, wie im folgenden Beispiel:
Zur Implementierung ist die Maps SDK for Android-Dienstprogrammbibliothek erforderlich.
Maps SDK for Android-Dienstprogrammbibliothek
Die Maps SDK for Android-Dienstprogrammbibliothek wurde erstellt, um die Funktionalität des Maps SDK for Android zu erweitern. Die Karte bietet erweiterte Funktionen wie Markierungscluster, Heatmaps, KML- und GeoJson-Unterstützung, Codierung von Polylinien und Decodierung sowie einige Hilfsfunktionen rund um sphärische Geometrie.
build.gradle-Datei aktualisieren
Da die Dienstprogrammbibliothek getrennt vom Maps SDK for Android verpackt ist, müssen Sie Ihrer build.gradle
-Datei eine zusätzliche Abhängigkeit hinzufügen.
- Aktualisiere den Abschnitt
dependencies
deinerapp/build.gradle
-Datei.
build.gradle
implementation 'com.google.maps.android:android-maps-utils:1.1.0'
- Nachdem Sie diese Zeile hinzugefügt haben, müssen Sie eine Projektsynchronisierung durchführen, um die neuen Abhängigkeiten abzurufen.
Clustering implementieren
So implementieren Sie das Clustering in Ihrer Anwendung:
- Implementiere die
ClusterItem
-Schnittstelle. - Unterklasse
DefaultClusterRenderer
. - Erstellen Sie einen
ClusterManager
und fügen Sie Elemente hinzu.
ClusterItem-Schnittstelle implementieren
Für alle Objekte, die eine clusterfähige Markierung auf der Karte darstellen, muss die ClusterItem
-Schnittstelle implementiert werden. In diesem Fall muss das Place
-Modell der ClusterItem
entsprechen. Öffne die Datei Place.kt
und nimm folgende Ä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
}
ClusterElement definiert diese drei Methoden:
getPosition()
steht für denLatLng
des Orts.getTitle()
steht für den Namen des OrtsgetSnippet()
steht für die Adresse des Orts.
Unterklasse DefaultClusterRenderer-Klasse
Die Klasse, die für die Implementierung von Clustern (ClusterManager
) zuständig ist, verwendet intern die Klasse ClusterRenderer
, um die Cluster zu erstellen, während Sie die Karte schwenken oder zoomen. Standardmäßig wird der Standard-Renderer DefaultClusterRenderer
verwendet, mit dem ClusterRenderer
implementiert wird. Bei einfachen Fällen reicht das aus. In diesem Fall müssen Sie jedoch die Klasse verlängern und die Anpassungen 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
}
}
Diese Klasse überschreibt diese beiden Funktionen:
onBeforeClusterItemRendered()
, der aufgerufen wird, bevor der Cluster auf der Karte gerendert wird Hier können Sie Anpassungen überMarkerOptions
vornehmen. In diesem Fall werden der Titel, die Position und das Symbol der Markierung festgelegt.onClusterItemRenderer()
, der direkt nach dem Rendern der Markierung auf der Karte aufgerufen wird. Hier kannst du auf das erstellteMarker
-Objekt zugreifen. In diesem Fall wird die Tag-Eigenschaft der Markierung festgelegt.
ClusterManager erstellen und Elemente hinzufügen
Abschließend müssen Sie MainActivity
ändern, um eine ClusterManager
zu instanziieren und die erforderlichen Abhängigkeiten dafür bereitzustellen. ClusterManager
verarbeitet die Markierungen (die ClusterItem
-Objekte) intern. Statt Markierungen direkt auf der Karte hinzuzufügen, wird diese Verantwortung an ClusterManager
delegiert. Außerdem ruft ClusterManager
intern setInfoWindowAdapter()
auf. Daher muss ein benutzerdefiniertes Infofenster über das MarkerManager.Collection
-Objekt von ClusterManger
festgelegt werden.
- Ändern Sie als Erstes den Inhalt des Lambda-Elements im
getMapAsync()
-Aufruf inMainActivity.onCreate()
. Kommentieren Sie den Aufruf vonaddMarkers()
undsetInfoWindowAdapter()
und rufen Sie stattdessen die MethodeaddClusteredMarkers()
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))
}
- Definieren Sie als Nächstes in
MainActivity
addClusteredMarkers()
.
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()
}
}
Bei dieser Methode wird eine ClusterManager
instanziiert, der benutzerdefinierte Renderer PlacesRenderer
an sie übergeben, alle Orte hinzugefügt und die Methode cluster()
aufgerufen. Da ClusterManager
die Methode setInfoWindowAdapter()
für das Kartenobjekt verwendet, muss das Fenster für benutzerdefinierte Informationen für das ClusterManager.markerCollection
-Objekt festgelegt werden. Schließlich möchten Sie, dass sich das Clustering ändert, wenn der Nutzer die Karte schwenkt und zoomt. Aus diesem Grund wird ein OnCameraIdleListener
an googleMap
gesendet, sodass clusterManager.onCameraIdle()
, wenn die Kamera inaktiv wird, aufgerufen wird.
- Führe die App aus, um dir die neuen Cluster anzusehen.
9. Auf der Karte zeichnen
Auch wenn Sie bereits versucht haben, durch Hinzufügen von Markierungen eine Karte zu zeichnen, unterstützt das Maps SDK for Android zahlreiche andere Möglichkeiten, nützliche Informationen auf der Karte anzuzeigen.
Wenn Sie beispielsweise Routen und Gebiete auf der Karte darstellen möchten, können Sie dafür Polylinien und Polygone verwenden. Wenn du ein Bild auf der Bodenoberfläche korrigieren möchtest, kannst du auch Boden-Overlays verwenden.
In dieser Aufgabe lernen Sie, wie Sie Formen umranden, mit denen Sie eine Markierung zeichnen, insbesondere einen Kreis.
Klick-Listener hinzufügen
Normalerweise würden Sie einen Klick-Listener zu einer Markierung hinzufügen, indem Sie einen Klick-Listener direkt über das GoogleMap
-Objekt über setOnMarkerClickListener()
ü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 von dieser Methode false
zurückgegeben, um anzugeben, dass die Methode dieses Ereignis nicht verarbeitet hat.
- Als Nächstes musst du die Property
circle
und die MethodeaddCircle()
inMainActivity
definieren.
Hauptaktivität.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))
)
}
Die Eigenschaft circle
ist so festgelegt, dass beim Tippen auf eine neue Markierung der vorherige Kreis und die neue Markierung entfernt werden. Die API zum Hinzufügen eines Kreises ähnelt der eines Markers.
- Führen Sie nun die App aus und sehen Sie sich die Änderungen an.
10. Kamerasteuerung
Als letzte Aufgabe sehen Sie sich einige Kamerasteuerungen an, mit denen Sie die Ansicht auf einen bestimmten Bereich ausrichten können.
Kamera und Ansicht
Wenn Sie bemerken, dass die App ausgeführt wird, zeigt die Kamera den Kontinent Afrika an. Sie müssen dann ungehindert schwenken und zoomen, um die hinzugefügten Markierungen zu finden. Es kann eine witzige Art sein, die Welt zu entdecken, es ist aber nicht sinnvoll, wenn du die Markierungen sofort anzeigen möchtest.
Du kannst dazu die Position der Kamera programmatisch festlegen, damit die Ansicht an der gewünschten Stelle zentriert wird.
- Fügen Sie den folgenden Code in den
getMapAsync()
-Aufruf ein, um die Kameraansicht so anzupassen, dass sie nach dem Start der App in 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 der setOnMapLoadedCallback()
aufgerufen, damit das Kameraupdate erst ausgeführt wird, nachdem die Karte geladen wurde. Dieser Schritt ist erforderlich, weil die Karteneigenschaften, z. B. Dimensionen, berechnet werden müssen, bevor ein Aufruf für eine Kameraaktualisierung durchgeführt wird.
Im Lambda wird ein neues LatLngBounds
-Objekt erstellt, das einen rechteckigen Bereich auf der Karte definiert. Dabei werden alle Place LatLng
-Werte einbezogen, um sicherzustellen, dass sich alle Orte innerhalb der Grenzen befinden. Sobald dieses Objekt erstellt wurde, wird die Methode moveCamera()
in GoogleMap
aufgerufen und über CameraUpdateFactory.newLatLngBounds(bounds.build(), 20)
wird eine CameraUpdate
bereitgestellt.
- Starten Sie die App und stellen Sie fest, dass die Kamera jetzt in San Francisco initialisiert ist.
Änderungen der Kamera erfassen
Sie können nicht nur die Kameraposition ändern, sondern auch die Kameraaktualisierungen beobachten, wenn sich der Nutzer auf der Karte bewegt. Dies kann nützlich sein, wenn Sie die UI ändern möchten, während sich die Kamera bewegt.
Aus praktischen Gründen lässt sich der Code so anpassen, dass die Markierungen durchscheinen, wenn die Kamera bewegt wird.
- In der
addClusteredMarkers()
-Methode müssen Sie unten die folgenden Zeilen hinzufügen:
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, sodass jedes Mal, wenn die Kamera bewegt wird, alle Alphawerte (Markierungen und Cluster) in 0.3f
geändert werden, sodass die Markierungen durchscheinend erscheinen.
- Ändern Sie abschließend den Inhalt von
setOnCameraIdleListener
in der MethodeaddClusteredMarkers()
so, dass die durchscheinenden Markierungen wieder undurchsichtig sind:
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ühre 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, stehen Kotlin-Erweiterungen oder KTX-Bibliotheken zur Verfügung, damit Sie die Kotlin-Sprachfunktionen wie Koroutinen, Erweiterungseigenschaften und -funktionen nutzen können. Für jedes Google Maps SDK ist die entsprechende KTX-Bibliothek zu sehen:
Für diese Aufgabe verwenden Sie die Maps KTX- und Maps Utils KTX-Bibliotheken in Ihrer App und refaktorieren vorherige Aufgaben. So können Sie Kotlin-spezifische Sprachfunktionen in Ihrer App nutzen.
- KTX-Abhängigkeiten in die build.gradle-Datei auf App-Ebene aufnehmen
Da die App sowohl die Maps SDK for Android-Dienstprogrammbibliothek als auch die Maps SDK for Android-Dienstprogrammbibliothek verwendet, müssen Sie die entsprechenden KTX-Bibliotheken für diese Bibliotheken hinzufügen. Außerdem wirst du bei dieser Aufgabe eine Funktion nutzen, die du in der AndroidX-Lebenszyklus-KTX-Bibliothek findest. Füge diese Abhängigkeit auch in deine 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'
}
- Funktionen der Erweiterung „GoogleMap.addMarker()“ und „GoogleMap.addCircle()“ verwenden
Die Maps KTX-Bibliothek bietet eine DSL-API-Alternative für GoogleMap.addMarker(MarkerOptions)
und GoogleMap.addCircle(CircleOptions)
, die in den vorherigen Schritten verwendet wurden. Zur Verwendung der oben genannten APIs ist die Erstellung einer Klasse, die Optionen für eine Markierung oder einen Kreis enthält, erforderlich. Mit den KTX-Alternativen können Sie die Markierungs- oder Kreisoptionen in dem von Ihnen angegebenen Lambda festlegen.
Wenn du diese APIs verwenden möchtest, aktualisiere 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 beschriebene Umformung der Verfahren ist deutlich kompakter, was mit dem Funktionsliteral mit Receiver von Kotlin möglich ist.
- Funktion „SupportMapFragment.awaitMap()“ und „GoogleMap.awaitMapLoad()“ zum Sperren verwenden
Die Maps KTX-Bibliothek bietet auch ausgesetzte Funktionserweiterungen an, die in Koroutinen verwendet werden können. Insbesondere gibt es Funktionsalternativen für SupportMapFragment.getMapAsync(OnMapReadyCallback)
und GoogleMap.setOnMapLoadedCallback(OnMapLoadedCallback)
aus. Wenn Sie diese alternativen APIs verwenden, müssen Sie keine Callbacks übergeben. Stattdessen erhalten Sie eine solche Antwort synchron und synchron.
Da diese Methoden Funktionen sperren, muss die Nutzung innerhalb einer Koroutine erfolgen. Die Bibliothek Lebenszyklus-Laufzeit KTX bietet eine Erweiterung zum Bereitstellen von Koroutinen, die für den Lebenszyklus relevant sind, sodass Koroutinen beim richtigen Lebenszyklusereignis ausgeführt und gestoppt werden.
Wenn du diese Konzepte kombinierst, aktualisiere die MainActivity.onCreate(Bundle)
-Methode:
MainActivity.onCreate(Set)
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 Koroutine-Bereich lifecycleScope.launchWhenCreated
führt den Block aus, wenn die Aktivität zumindest im Erstellungsstatus ist. Die Aufrufe zum Abrufen des GoogleMap
-Objekts und warten, bis die Karte vollständig geladen ist, wurden durch SupportMapFragment.awaitMap()
bzw. GoogleMap.awaitMapLoad()
ersetzt. Wenn Sie den Code mit diesen Sperrfunktionen refaktorieren, können Sie den entsprechenden Callback-basierten Code der Reihe nach schreiben.
- Erstellen Sie die App mit Ihren refaktorierten Änderungen neu.
12. Glückwunsch
Glückwunsch! Du hast viele Inhalte behandelt und wir hoffen, dass du hier die Kernfunktionen des Maps SDK for Android besser verstehst.
Weitere Informationen
- Places SDK for Android: Anhand einer Vielzahl von Ortsdaten können Sie Unternehmen in Ihrer Nähe 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-freundlich einbinden können.
- android-place-ktx: eine Open-Source-Bibliothek, mit der Sie das Places SDK for Android Kotlin-freundlich nutzen können
- android-samples: Beispielcode auf GitHub, in dem alle Funktionen, die in diesem Codelab behandelt werden, und mehr behandelt werden
- Weitere Kotlin-Codelabs zum Erstellen von Android-Apps mit der Google Maps Platform