Karte in Ihre Android-App einfügen (Kotlin)

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.

f05e1ca27ff42bf6

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

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.

  1. 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.

  1. 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.
  2. 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.

  1. 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.

  1. 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.

d1d068b5d4ae38b9.png

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.

  1. Öffnen Sie dazu die Datei mit dem Namen local.properties im Stammverzeichnis Ihres Projekts. Das ist die Ebene, in der sich gradle.properties und settings.gradle befinden.
  2. 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.

  1. 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 Verzeichnis app/ einfügen und die folgende Zeile im plugins-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

  1. 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'
}
  1. Als Nächstes fügen Sie in AndroidManifest.xml ein neues meta-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 folgende meta-data-Tag in das application-Objekt in deine AndroidManifest.xml-Datei ein, die sich in app/src/main befindet.

AndroidManifest.xml

<meta-data
   android:name="com.google.android.geo.API_KEY"
   android:value="${GOOGLE_MAPS_API_KEY}" />
  1. Erstelle als Nächstes eine neue Layoutdatei namens activity_main.xml im Verzeichnis app/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.

  1. Ergänzen Sie schließlich die Klasse MainActivity in app/src/main/java/com/google/codelabs/buildyourfirstmap, indem Sie den folgenden Code hinzufügen, um die Methode onCreate 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)
}
  1. 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:

  1. Erstellen Sie eine Karten-ID.
  2. 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.

bc5576877369b554

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.

  1. Erstellen Sie in MainActivity die Methode addMarkers() 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.

  1. Ö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.

a26f82802fe838e9.png

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.

  1. 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.

8cac51fcbef9171b

  1. Geben Sie im Dialogfeld marker_info_contents in das Feld File name (Dateiname) und LinearLayout in das Feld Root element ein. Klicken Sie dann auf OK.

8783af12baf07a80

Diese Layoutdatei wird später erhöht, um den Inhalt des Infofensters zu darstellen.

  1. Kopieren Sie den Inhalt im folgenden Code-Snippet, das drei TextViews in einer vertikalen LinearLayout-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.

  1. Erstellen Sie eine neue Kotlin-Datei namens MarkerInfoWindowAdapter im selben Paket wie MainActivity. Klicken Sie dazu in Android Studio in der Projektansicht mit der rechten Maustaste auf den Ordner app/src/main/java/com/google/codelabs/buildyourfirstmap und wählen Sie Neu > Kotlin Datei/Klasse aus.

3975ba36eba9f8e1.png

  1. Geben Sie im Dialogfeld MarkerInfoWindowAdapter ein und lassen Sie Datei markiert.

992235af53d3897f

  1. 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.

  1. Füge dazu den folgenden Code nach dem addMarkers()-Methodeaufruf im getMapAsync()-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.

  1. Ändern Sie dazu den Aufruf von places.forEach{} in der Funktion addMarkers() 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.

6eb7358bb61b0a88

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.

  1. Rufe dazu mit dieser Property die icon-Methode von MarkerOptions in der addMarkers()-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)
)
  1. 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.

68591 Ed88D73724

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:

f05e1ca27ff42bf6

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.

  1. Aktualisiere den Abschnitt dependencies deiner app/build.gradle-Datei.

build.gradle

implementation 'com.google.maps.android:android-maps-utils:1.1.0'
  1. Nachdem Sie diese Zeile hinzugefügt haben, müssen Sie eine Projektsynchronisierung durchführen, um die neuen Abhängigkeiten abzurufen.

b7b030ec82c007fd.png

Clustering implementieren

So implementieren Sie das Clustering in Ihrer Anwendung:

  1. Implementiere die ClusterItem-Schnittstelle.
  2. Unterklasse DefaultClusterRenderer.
  3. 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 den LatLng des Orts.
  • getTitle() steht für den Namen des Orts
  • getSnippet() 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 über MarkerOptions 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 erstellte Marker-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.

  1. Ändern Sie als Erstes den Inhalt des Lambda-Elements im getMapAsync()-Aufruf in MainActivity.onCreate(). Kommentieren Sie den Aufruf von addMarkers() und setInfoWindowAdapter() und rufen Sie stattdessen die Methode addClusteredMarkers() 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))
}
  1. 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.

  1. 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.

f98ce13055430352.png.

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.

  1. Fügen Sie in der Methode addClusteredMarkers() in MainActivity die folgende Zeile direkt nach dem Aufruf von cluster() 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.

  1. Als Nächstes musst du die Property circle und die Methode addCircle() in MainActivity 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. Ändern Sie abschließend den Inhalt von setOnCameraIdleListener in der Methode addClusteredMarkers() 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()
}
  1. 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:

Diagramm zur Google Maps Platform-KTX

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.

  1. 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'
}
  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.

  1. 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.

  1. 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