Menambahkan peta ke aplikasi Android (Kotlin)

Tetap teratur dengan koleksi Simpan dan kategorikan konten berdasarkan preferensi Anda.

1. Sebelum Memulai

Codelab ini akan mengajarkan cara mengintegrasikan Maps SDK for Android dengan aplikasi Anda dan menggunakan fitur intinya dengan membuat aplikasi yang menampilkan peta toko sepeda di San Francisco, CA, AS.

f05e1ca27ff42bf6.png

Prasyarat

  • Pengetahuan dasar tentang pengembangan Kotlin dan Android

Yang akan Anda lakukan

  • Mengaktifkan dan menggunakan Maps SDK for Android untuk menambahkan Google Maps ke aplikasi Android.
  • Menambahkan, menyesuaikan, dan mengelompokkan penanda.
  • Menggambar polyline dan poligon pada peta.
  • Mengontrol titik pandang kamera secara terprogram.

Yang akan Anda perlukan

2. Memulai persiapan

Untuk langkah pengaktifan berikut , Anda harus mengaktifkan Maps SDK for Android.

Menyiapkan Google Maps Platform

Jika Anda belum memiliki akun Google Cloud Platform dan project dengan penagihan diaktifkan, lihat panduan Memulai Google Maps Platform untuk membuat akun penagihan dan project.

  1. Di Cloud Console, klik menu drop-down project lalu pilih project yang ingin Anda gunakan untuk codelab ini.

  1. Aktifkan API dan SDK Google Maps Platform yang diperlukan untuk codelab ini di Google Cloud Marketplace. Untuk melakukannya, ikuti langkah-langkah dalam video ini atau dokumentasi ini.
  2. Buat kunci API di halaman Kredensial di Cloud Console. Anda dapat mengikuti langkah-langkah dalam video ini atau dokumentasi ini. Semua permintaan ke Google Maps Platform memerlukan kunci API.

3. Mulai cepat

Untuk membantu Anda memulai secepatnya, berikut beberapa kode awal untuk membantu Anda mengikuti codelab ini. Anda dapat langsung ke bagian solusi, namun jika Anda ingin mengikuti semua langkah untuk membuatnya sendiri, baca semuanya.

  1. Lakukan clone repositori jika sudah menginstal git.
git clone https://github.com/googlecodelabs/maps-platform-101-android.git

Atau, Anda dapat mengklik tombol berikut untuk mendownload kode sumber.

  1. Setelah mendapatkan kode, lanjutkan dan buka project yang ada dalam direktori starter di Android Studio.

4. Menambahkan Google Maps

Di bagian ini, Anda akan menambahkan Google Maps sehingga dimuat saat aplikasi diluncurkan.

d1d068b5d4ae38b9.png

Menambahkan kunci API

Kunci API yang Anda buat di langkah sebelumnya harus diberikan ke aplikasi sehingga Maps SDK for Android dapat mengaitkan kunci dengan aplikasi Anda.

  1. Untuk memberikannya, buka file bernama local.properties di direktori utama project Anda (level yang sama tempat gradle.properties dan settings.gradle berada).
  2. Di file tersebut, tentukan kunci baru GOOGLE_MAPS_API_KEY dengan nilainya menjadi kunci API yang Anda buat.

local.properties

GOOGLE_MAPS_API_KEY=YOUR_KEY_HERE

Perhatikan bahwa local.properties dicantumkan dalam file .gitignore di repositori Git. Hal ini karena kunci API Anda dianggap sebagai informasi sensitif dan tidak boleh diupload ke kontrol sumber, jika memungkinkan.

  1. Selanjutnya, untuk mengekspos API Anda agar dapat digunakan di seluruh aplikasi, sertakan plugin Plugin Secrets Gradle untuk Android di file build.gradle aplikasi yang terletak di direktori app/ dan tambahkan baris berikut dalam blok plugins:

build.gradle tingkat aplikasi

plugins {
    // ...
    id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
}

Anda juga perlu memodifikasi file build.gradle level project untuk menyertakan classpath berikut:

build.gradle tingkat project

buildscript {
    dependencies {
        // ...
        classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:1.3.0"
    }
}

Plugin ini akan membuat kunci yang telah Anda tentukan dalam file local.properties tersedia sebagai variabel build dalam file manifes Android dan sebagai variabel dalam class BuildConfig yang dihasilkan Gradle pada waktu build. Menggunakan plugin ini akan menghapus kode boilerplate yang seharusnya diperlukan untuk membaca properti dari local.properties, sehingga dapat diakses di seluruh aplikasi Anda.

Menambahkan dependensi Google Maps

  1. Setelah kunci API Anda bisa diakses dalam aplikasi, langkah berikutnya adalah menambahkan dependensi Maps SDK for Android ke file build.gradle aplikasi Anda.

Dalam project permulaan yang disertakan bersama codelab ini, dependensi ini telah ditambahkan untuk Anda.

build.gradle

dependencies {
   // Dependency to include Maps SDK for Android
   implementation 'com.google.android.gms:play-services-maps:17.0.0'
}
  1. Selanjutnya, tambahkan tag meta-data baru di AndroidManifest.xml untuk meneruskan kunci API yang Anda buat di langkah sebelumnya. Untuk melakukannya, lanjutkan dan buka file ini di Android Studio lalu tambahkan tag meta-data berikut dalam objek application di file AndroidManifest.xml, yang terletak di app/src/main.

AndroidManifest.xml

<meta-data
   android:name="com.google.android.geo.API_KEY"
   android:value="${GOOGLE_MAPS_API_KEY}" />
  1. Selanjutnya, buat file tata letak baru yang bernama activity_main.xml di direktori app/src/main/res/layout/ dan tentukan sebagai berikut:

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>

Tata letak ini memiliki satu FrameLayout yang berisi SupportMapFragment. Fragmen ini berisi objek GoogleMaps dasar yang Anda gunakan di langkah-langkah selanjutnya.

  1. Terakhir, update class MainActivity yang berada di app/src/main/java/com/google/codelabs/buildyourfirstmap dengan menambahkan kode berikut untuk mengganti metode onCreate agar Anda dapat menetapkan kontennya dengan tata letak baru yang baru saja Anda buat.

MainActivity

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)
}
  1. Sekarang, lanjutkan dan jalankan aplikasi. Anda akan melihat beban peta di layar perangkat Anda.

5. Penataan gaya peta berbasis cloud (Opsional)

Anda dapat menyesuaikan gaya peta menggunakan Penataan gaya peta berbasis cloud.

Membuat ID Peta

Jika Anda belum membuat ID peta dengan gaya peta terkait, lihat panduan ID Peta untuk menyelesaikan langkah-langkah berikut:

  1. Membuat ID peta.
  2. Mengaitkan ID peta ke gaya peta.

Menambahkan ID Peta ke aplikasi Anda

Untuk menggunakan ID peta yang Anda buat, ubah file activity_main.xml dan teruskan ID peta Anda di atribut map:mapId dari 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" />

Setelah menyelesaikannya, jalankan aplikasi untuk melihat peta dengan gaya yang Anda pilih.

6. Menambahkan penanda

Dalam tugas ini, Anda menambahkan penanda ke peta yang mewakili lokasi menarik yang ingin Anda sorot pada peta. Pertama, Anda mengambil daftar tempat yang telah diberikan dalam project permulaan untuk Anda, kemudian tambahkan tempat tersebut ke peta. Dalam contoh ini digunakan toko sepeda.

bc5576877369b554.png

Mendapatkan referensi untuk GoogleMap

Pertama, Anda perlu mendapatkan referensi untuk objek GoogleMap sehingga dapat menggunakan metodenya. Untuk melakukannya, tambahkan kode berikut ke metode MainActivity.onCreate() Anda, tepat setelah panggilan ke setContentView():

MainActivity.onCreate()

val mapFragment = supportFragmentManager.findFragmentById(
    R.id.map_fragment
) as? SupportMapFragment
mapFragment?.getMapAsync { googleMap ->
    addMarkers(googleMap)
}

Penerapan awalnya akan menemukan SupportMapFragment yang Anda tambahkan di langkah sebelumnya dengan menggunakan metode findFragmentById() pada objek SupportFragmentManager. Setelah referensi diperoleh, panggilan getMapAsync() dipanggil dan diikuti dengan meneruskan lambda. Lambda ini adalah tempat objek GoogleMap diteruskan. Di dalam lambda ini, panggilan metode addMarkers() dipanggil, yang didefinisikan dalam waktu singkat.

Class yang disediakan: PlacesReader

Dalam project permulaan, class PlacesReader telah disediakan untuk Anda. Class ini membaca daftar berisi 49 tempat yang disimpan dalam file JSON bernama places.json dan menampilkannya sebagai List<Place>. Tempat-tempat itu sendiri mewakili daftar toko sepeda di sekitar San Francisco, CA, AS.

Jika penasaran dengan penerapan class ini, Anda dapat mengaksesnya di GitHub atau membuka class PlacesReader di Android Studio.

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()
       }
   }

Memuat tempat

Untuk memuat daftar toko sepeda, tambahkan properti di MainActivity yang bernama places dan tentukan sebagai berikut:

MainActivity.places

private val places: List<Place> by lazy {
   PlacesReader(this).read()
}

Kode ini memanggil metode read() pada PlacesReader, yang menampilkan List<Place>. Place memiliki properti yang disebut name, nama tempat tersebut, dan latLng, koordinat tempat tersebut.

Tempat

data class Place(
   val name: String,
   val latLng: LatLng,
   val address: LatLng,
   val rating: Float
)

Menambahkan penanda ke peta

Setelah daftar tempat dimuat ke memori, langkah berikutnya adalah menampilkan tempat ini pada peta.

  1. Buat metode di MainActivity yang bernama addMarkers() dan tentukan sebagai berikut:

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)
       )
   }
}

Metode ini melakukan iterasi melalui daftar places yang diikuti dengan memanggil metode addMarker() di objek GoogleMap yang disediakan. Penanda tersebut dibuat dengan membuat instance objek MarkerOptions, yang memungkinkan Anda menyesuaikan penanda itu sendiri. Dalam hal ini, judul dan posisi penanda diberikan, yang masing-masing mewakili nama toko sepeda dan koordinatnya.

  1. Lanjutkan dan jalankan aplikasi, lalu buka San Francisco untuk melihat penanda yang baru saja Anda tambahkan.

7. Menyesuaikan penanda

Ada beberapa opsi penyesuaian untuk penanda yang baru saja Anda tambahkan untuk membantu menonjolkan penanda dan memberikan informasi yang berguna kepada pengguna. Dalam tugas ini, Anda akan menjelajahi beberapa di antaranya dengan menyesuaikan gambar setiap penanda serta jendela informasi yang ditampilkan saat penanda diketuk.

a26f82802fe838e9.png

Menambahkan jendela info

Secara default, jendela info saat Anda mengetuk penanda akan menampilkan judul dan cuplikannya (jika ditetapkan). Anda menyesuaikannya sehingga dapat menampilkan informasi tambahan, seperti alamat dan rating tempat.

Membuat marker_info_contents.xml

Pertama, buat file tata letak baru bernama marker_info_contents.xml.

  1. Untuk melakukannya, klik kanan pada folder app/src/main/res/layout di tampilan project di Android Studio lalu pilih New > Layout Resource File.

8cac51fcbef9171b.png

  1. Pada dialog, ketik marker_info_contents di kolom File name dan LinearLayout di kolom Root element, lalu klik OK.

8783af12baf07a80.png

File tata letak ini kemudian di-inflate untuk menampilkan konten dalam jendela info.

  1. Salin konten dalam cuplikan kode berikut, yang menambahkan tiga TextViews dalam kelompok tampilan LinearLayout vertikal, dan menimpa kode default dalam file.

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>

Membuat penerapan InfoWindowAdapter

Setelah membuat file tata letak untuk jendela info kustom, langkah berikutnya adalah menerapkan antarmuka GoogleMap.InfoWindowAdapter. Antarmuka ini berisi dua metode, getInfoWindow() dan getInfoContents(). Kedua metode ini menampilkan objek View opsional, yang pertama digunakan untuk menyesuaikan jendela itu sendiri, sementara yang kedua untuk menyesuaikan kontennya. Dalam kasus ini, Anda menerapkan keduanya dan menyesuaikan tampilan getInfoContents() saat menampilkan null di getInfoWindow(), yang menunjukkan bahwa jendela default harus digunakan.

  1. Buat file Kotlin baru yang bernama MarkerInfoWindowAdapter dalam paket yang sama seperti MainActivity dengan mengklik kanan folder app/src/main/java/com/google/codelabs/buildyourfirstmap dalam tampilan project di Android Studio, lalu pilih New > Kotlin File/Class.

3975ba36eba9f8e1.png

  1. Pada dialog, ketik MarkerInfoWindowAdapter dan biarkan File tetap disorot.

992235af53d3897f.png

  1. Setelah Anda membuat file tersebut, salin konten dalam cuplikan kode berikut ke file baru Anda.

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
   }
}

Dalam konten metode getInfoContents(), Penanda yang disediakan dalam metode ini ditransmisikan ke jenis Place, dan jika transmisi tidak memungkinkan, metode akan menampilkan null (Anda belum menetapkan properti tag pada Marker, tetapi Anda dapat melakukannya di langkah berikutnya).

Selanjutnya, tata letak marker_info_contents.xml di-inflate, lalu teks yang berisi TextViews ditetapkan ke tag Place.

Mengupdate MainActivity

Untuk menempelkan semua komponen yang telah Anda buat sejauh ini, Anda perlu menambahkan dua baris di class MainActivity.

Pertama, untuk meneruskan InfoWindowAdapter, MarkerInfoWindowAdapter kustom di dalam panggilan metode getMapAsync, panggil metode setInfoWindowAdapter() pada objek GoogleMap lalu buat instance baru MarkerInfoWindowAdapter.

  1. Lakukan hal ini dengan menambahkan kode berikut setelah panggilan metode addMarkers() di dalam lambda getMapAsync().

MainActivity.onCreate()

// Set custom info window adapter
googleMap.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))

Terakhir, Anda harus menetapkan setiap Place sebagai properti tag pada setiap Penanda yang ditambahkan ke peta.

  1. Untuk melakukannya, ubah panggilan places.forEach{} dalam fungsi addMarkers() dengan yang berikut:

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
}

Menambahkan gambar penanda kustom

Menyesuaikan gambar penanda adalah salah satu cara seru untuk mengomunikasikan jenis tempat yang diwakili penanda pada peta Anda. Untuk langkah ini, Anda menampilkan sepeda, bukan penanda merah default untuk mewakili setiap toko pada peta. Project permulaan mencakup ikon sepeda ic_directions_bike_black_24dp.xml di app/src/res/drawable, yang Anda gunakan.

6eb7358bb61b0a88.png

Menetapkan bitmap kustom pada penanda

Dengan ikon sepeda vektor drawable, langkah berikutnya adalah menetapkan drawable tersebut sebagai ikon penanda masing-masing pada peta. MarkerOptions memiliki metode icon, yang menyertakan BitmapDescriptor yang Anda gunakan untuk melakukan ini.

Pertama-tama, Anda perlu mengonversi vektor drawable yang baru saja ditambahkan ke BitmapDescriptor. File bernama BitMapHelper yang disertakan dalam project permulaan berisi fungsi bantuan yang disebut vectorToBitmap(), yang melakukan hal tersebut.

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)
   }
}

Metode ini menyertakan Context, sebuah ID resource drawable, serta integer warna, dan membuat representasi BitmapDescriptor dari keduanya.

Dengan menggunakan metode bantuan, deklarasikan properti baru yang disebut bicycleIcon dan berikan definisi berikut: 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)
}

Properti ini menggunakan warna standar colorPrimary di aplikasi Anda, serta menggunakannya untuk memberikan warna pada ikon sepeda dan menampilkannya sebagai BitmapDescriptor.

  1. Menggunakan properti ini, lanjutkan dan panggil metode icon dari MarkerOptions di metode addMarkers() untuk menyelesaikan penyesuaian ikon. Setelah melakukan ini, properti penanda seharusnya terlihat seperti berikut:

MainActivity.addMarkers()

val marker = googleMap.addMarker(
    MarkerOptions()
        .title(place.name)
        .position(place.latLng)
        .icon(bicycleIcon)
)
  1. Jalankan aplikasi untuk melihat penanda yang telah diupdate.

8 Penanda cluster

Bergantung pada tingkat zoom yang digunakan pada peta, Anda mungkin melihat bahwa penanda yang Anda tambahkan tumpang-tindih. Sangat sulit untuk berinteraksi dengan penanda yang tumpang-tindih karena menghasilkan banyak derau, yang akan berdampak pada kegunaan aplikasi Anda.

68591edc86d73724.png

Untuk meningkatkan kualitas pengalaman pengguna terkait hal ini, setiap kali Anda memiliki set data besar yang dikelompokkan berdekatan, praktik terbaiknya adalah menerapkan pengelompokan penanda. Dengan pengelompokan, saat Anda memperbesar dan memperkecil peta, penanda yang letaknya berdekatan akan dikelompokkan bersama seperti ini:

f05e1ca27ff42bf6.png

Untuk menerapkan ini, Anda memerlukan bantuan Library Utilitas Maps SDK for Android.

Library Utilitas Maps SDK for Android

Library Utilitas Maps SDK for Android dibuat sebagai cara untuk memperluas fungsi Maps SDK for Android. Library ini menawarkan fitur lanjutan, seperti pengelompokan penanda, peta panas, dukungan KML dan GeoJson, encoding dan decoding polyline, serta beberapa fungsi bantuan di sekeliling geometri bundar.

Mengupdate build.gradle

Karena library utilitas dipaketkan secara terpisah dari Maps SDK for Android, Anda perlu menambahkan dependensi tambahan ke file build.gradle.

  1. Lanjutkan dan update bagian dependencies pada file app/build.gradle Anda.

build.gradle

implementation 'com.google.maps.android:android-maps-utils:1.1.0'
  1. Setelah menambahkan baris ini, Anda harus melakukan sinkronisasi project untuk mengambil dependensi baru.

b7b030ec82c007fd.png

Menerapkan pengelompokan

Untuk menerapkan pengelompokan di aplikasi Anda, ikuti tiga langkah berikut:

  1. Terapkan antarmuka ClusterItem.
  2. Buat subclass untuk class DefaultClusterRenderer.
  3. Buat ClusterManager dan tambahkan item.

Menerapkan antarmuka ClusterItem

Semua objek yang mewakili penanda yang dapat dikelompokkan pada peta perlu menerapkan antarmuka ClusterItem. Dalam kasus Anda, ini artinya model Place harus sesuai dengan ClusterItem. Lanjutkan dan buka file Place.kt lalu lakukan perubahan berikut:

Tempat

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
}

ClusterItem menentukan tiga metode ini:

  • getPosition(), yang mewakili LatLng tempat.
  • getTitle(), yang mewakili nama tempat
  • getSnippet(), yang mewakili alamat tempat.

Membuat subclass untuk class DefaultClusterRenderer

Class yang bertugas untuk menerapkan pengelompokan, ClusterManager, secara internal menggunakan class ClusterRenderer untuk menangani pembuatan cluster saat Anda menggeser dan melakukan zoom di sekeliling peta. Secara default disertakan perender default, DefaultClusterRenderer, yang menerapkan ClusterRenderer. Untuk kasus sederhana, ini seharusnya sudah cukup. Namun pada kasus Anda, karena penanda perlu disesuaikan, Anda harus memperluas class ini dan menambahkan penyesuaian di dalamnya.

Lanjutkan dan buat file Kotlin PlaceRenderer.kt dalam paket com.google.codelabs.buildyourfirstmap.place lalu tentukan sebagai berikut:

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
   }
}

Class ini mengganti dua fungsi berikut:

  • onBeforeClusterItemRendered(), yang dipanggil sebelum cluster dirender pada peta. Di sini, Anda dapat memberikan penyesuaian melalui MarkerOptions—pada kasus ini akan ditetapkan judul, posisi, dan ikon penanda.
  • onClusterItemRenderer(), yang dipanggil tepat setelah penanda dirender pada peta. Ini adalah tempat Anda dapat mengakses objek Marker yang dibuat—pada kasus ini akan ditetapkan properti tag penanda.

Membuat ClusterManager dan menambahkan item

Terakhir, agar pengelompokan berfungsi, Anda perlu memodifikasi MainActivity untuk membuat instance ClusterManager dan memberikan dependensi yang diperlukan. ClusterManager menangani penambahan penanda (objek ClusterItem) secara internal, jadi bukannya menambahkan penanda langsung ke peta, tanggung jawab ini didelegasikan ke ClusterManager. Selain itu, ClusterManager juga memanggil setInfoWindowAdapter() secara internal, sehingga menyetel jendela info kustom harus dilakukan pada objek MarkerManager.Collection ClusterManger.

  1. Untuk memulai, ubah konten lambda dalam panggilan getMapAsync() di MainActivity.onCreate(). Lanjutkan dan jadikan sebagai komentar panggilan ke addMarkers() dan setInfoWindowAdapter(), dan bukannya memanggil metode yang disebut addClusteredMarkers(), yang Anda tentukan berikutnya.

MainActivity.onCreate()

mapFragment?.getMapAsync { googleMap ->
    //addMarkers(googleMap)
    addClusteredMarkers(googleMap)

    // Set custom info window adapter.
    // googleMap.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))
}
  1. Selanjutnya, di MainActivity, tentukan 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()
   }
}

Metode ini membuat instance ClusterManager, meneruskan perender kustom PlacesRenderer ke lokasi ini, menambahkan semua tempat, dan memanggil metode cluster(). Selain itu, karena ClusterManager menggunakan metode setInfoWindowAdapter() pada objek peta, menyetel jendela info kustom harus dilakukan pada objek ClusterManager.markerCollection. Terakhir, karena Anda ingin pengelompokan berubah saat pengguna menggeser dan melakukan zoom di sekeliling peta, OnCameraIdleListener disediakan untuk googleMap, agar clusterManager.onCameraIdle() dipanggil saat tidak ada aktivitas kamera.

  1. Lanjutkan dan jalankan aplikasi untuk melihat toko baru yang dikelompokkan.

9. Gambar di peta

Meskipun Anda telah mempelajari satu cara untuk menggambar di peta (dengan menambahkan penanda), SDK Maps for Android mendukung berbagai cara lain untuk menggambar agar dapat menampilkan informasi yang berguna pada peta.

Misalnya, jika ingin menampilkan rute dan area pada peta, Anda dapat menggunakan polyline dan poligon untuk menampilkannya pada peta. Atau, jika Anda ingin memperbaiki gambar pada permukaan bumi, Anda dapat menggunakan overlay bumi.

Dalam tugas ini, Anda akan mempelajari cara menggambar bentuk, khususnya lingkaran, di sekeliling penanda setiap kali penanda diketuk.

f98ce13055430352.png

Menambahkan pemroses klik

Biasanya, cara Anda menambahkan pemroses klik ke penanda adalah dengan meneruskan pemroses klik langsung pada objek GoogleMap melalui setOnMarkerClickListener(). Namun, karena Anda menggunakan pengelompokan, pemroses klik harus diberikan ke ClusterManager.

  1. Pada metode addClusteredMarkers() di MainActivity, lanjutkan dan tambahkan baris berikut tepat setelah panggilan ke cluster().

MainActivity.addClusteredMarkers()

// Show polygon
clusterManager.setOnClusterItemClickListener { item ->
   addCircle(googleMap, item)
   return@setOnClusterItemClickListener false
}

Metode ini menambahkan pemroses dan memanggil metode addCircle(), yang Anda tentukan berikutnya. Terakhir, false ditampilkan dari metode ini untuk menunjukkan bahwa metode ini tidak menggunakan peristiwa ini.

  1. Selanjutnya, Anda perlu menentukan properti circle dan metode addCircle() di MainActivity.

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))
   )
}

Properti circle ditetapkan sehingga setiap kali penanda baru diketuk, lingkaran sebelumnya akan dihapus dan lingkaran baru ditambahkan. Perhatikan bahwa API untuk menambahkan lingkaran agak mirip dengan API untuk menambahkan penanda.

  1. Lanjutkan sekarang dan jalankan aplikasi untuk melihat perubahan.

10. Kontrol Kamera

Sebagai tugas terakhir Anda, lihat beberapa kontrol kamera sehingga Anda dapat berfokus pada tampilan di sekitar wilayah tertentu.

Kamera dan tampilan

Jika saat menjalankan aplikasi, Anda melihat kamera menampilkan benua Afrika dan Anda harus menggeser dan men-zoom ke San Francisco dengan sangat cermat untuk menemukan penanda yang Anda tambahkan. Meskipun bisa menjadi cara yang menyenangkan untuk menjelajahi dunia, ini tidak berguna jika Anda ingin langsung menampilkan penanda.

Untuk membantu mengatasi hal ini, Anda dapat menetapkan posisi kamera secara terprogram sehingga tampilan akan dipusatkan di tempat yang Anda inginkan.

  1. Lanjutkan dan tambahkan kode berikut ke panggilan getMapAsync() untuk menyesuaikan tampilan kamera sehingga akan diinisialisasi ke San Francisco saat aplikasi dibuka.

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))
   }
}

Pertama, setOnMapLoadedCallback() dipanggil sehingga kamera diupdate hanya setelah peta dimuat. Langkah ini diperlukan karena properti peta, seperti dimensi, harus dihitung sebelum melakukan panggilan update kamera.

Di lambda, objek LatLngBounds baru akan dibuat, yang menentukan wilayah persegi panjang pada peta. Ini dibuat secara bertahap dengan menyertakan semua nilai LatLng tempat di dalamnya untuk memastikan semua tempat berada dalam batas. Setelah objek ini dibuat, metode moveCamera() di GoogleMap dipanggil dan CameraUpdate diberikan melalui CameraUpdateFactory.newLatLngBounds(bounds.build(), 20).

  1. Jalankan aplikasi dan perhatikan bahwa kamera sekarang diinisialisasi di San Francisco.

Memproses perubahan kamera

Selain mengubah posisi kamera, Anda juga dapat memproses update kamera saat pengguna bergerak di sekeliling peta. Ini dapat berguna jika Anda ingin mengubah UI saat kamera bergerak berkeliling.

Untuk hiburan saja, Anda memodifikasi kode untuk membuat penanda transparan setiap kali kamera bergerak.

  1. Pada metode addClusteredMarkers(), lanjutkan dan tambahkan baris berikut di bagian bawah metode:

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 }
}

Ini menambahkan OnCameraMoveStartedListener sehingga setiap kali kamera mulai bergerak, semua nilai alfa penanda (cluster dan penanda) akan diubah menjadi 0.3f sehingga penanda tampak transparan.

  1. Terakhir, untuk memodifikasi penanda agar tidak lagi transparan saat kamera berhenti, ubah konten setOnCameraIdleListener dalam metode addClusteredMarkers() menjadi:

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. Lanjutkan dan jalankan aplikasi untuk melihat hasilnya.

11. KTX Maps

Untuk aplikasi Kotlin yang menggunakan satu atau beberapa Android SDK Google Maps Platform, ekstensi Kotlin atau library KTX tersedia untuk memungkinkan Anda memanfaatkan fitur bahasa Kotlin seperti coroutine, properti/fungsi ekstensi, dan lainnya. Setiap Google Maps SDK memiliki library KTX yang sesuai seperti yang ditunjukkan di bawah ini:

Diagram KTX Google Maps Platform

Dalam tugas ini, Anda akan menggunakan library KTX Maps dan Utilitas Maps Maps untuk aplikasi Anda dan memfaktorkan ulang implementasi tugas sebelumnya sehingga Anda dapat menggunakan fitur bahasa khusus Kotlin di aplikasi Anda.

  1. Sertakan dependensi KTX dalam file build.gradle tingkat aplikasi

Karena aplikasi menggunakan Maps SDK for Android dan Library Utilitas Maps SDK for Android, Anda harus menyertakan library KTX yang sesuai untuk library ini. Anda juga akan menggunakan fitur yang ditemukan dalam library KTX Siklus Proses AndroidX dalam tugas ini, jadi sertakan juga dependensi tersebut dalam file build.gradle level aplikasi Anda.

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. Menggunakan fungsi ekstensi GoogleMap.addMarker() dan GoogleMap.addCircle()

Library KTX Maps menyediakan alternatif API gaya DSL untuk GoogleMap.addMarker(MarkerOptions) dan GoogleMap.addCircle(CircleOptions) yang digunakan pada langkah sebelumnya. Untuk menggunakan API yang disebutkan di atas, pembuatan class yang berisi opsi untuk penanda atau lingkaran diperlukan, sedangkan dengan alternatif KTX, Anda dapat menyetel opsi penanda atau lingkaran dalam lambda yang Anda berikan.

Untuk menggunakan API ini, perbarui metode MainActivity.addMarkers(GoogleMap) dan 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))
    }
}

Menulis ulang metode di atas dengan cara ini jauh lebih ringkas untuk dibaca yang dimungkinkan menggunakan literal fungsi dengan penerima Kotlin.

  1. Menggunakan fungsi penangguhan ekstensi SupportMapFragment.awaitMap() dan GoogleMap.awaitMapLoad()

Library KTX Maps juga menyediakan ekstensi fungsi penangguhan untuk digunakan dalam coroutine. Secara khusus, ada alternatif fungsi yang ditangguhkan untuk SupportMapFragment.getMapAsync(OnMapReadyCallback) dan GoogleMap.setOnMapLoadedCallback(OnMapLoadedCallback). Menggunakan API alternatif ini meniadakan kebutuhan untuk meneruskan callback dan sebagai gantinya Anda dapat menerima respons metode-metode ini secara seri dan sinkron.

Karena metode ini menangguhkan fungsi, penggunaannya harus dilakukan dalam coroutine. Library Lifecycle Runtime KTX menawarkan ekstensi untuk menyediakan cakupan coroutine berbasis siklus proses sehingga coroutine dijalankan dan dihentikan pada peristiwa siklus proses yang sesuai.

Dengan menggabungkan konsep-konsep ini, perbarui metode MainActivity.onCreate(Bundle):

MainActivity.onCreate(Paket)

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)
    }
}

Cakupan coroutine lifecycleScope.launchWhenCreated akan menjalankan blok saat aktivitas berada setidaknya dalam status yang dibuat. Perhatikan juga bahwa panggilan untuk mengambil objek GoogleMap, dan menunggu peta selesai dimuat, masing-masing telah diganti dengan SupportMapFragment.awaitMap() dan GoogleMap.awaitMapLoad(). Memfaktorkan ulang kode menggunakan fungsi penangguhan ini memungkinkan Anda menulis kode berbasis callback yang setara secara berurutan.

  1. Lanjutkan dan build ulang aplikasi dengan perubahan yang telah difaktorkan ulang.

12. Selamat

Selamat! Anda telah mempelajari begitu banyak konten dan semoga Anda memiliki pemahaman yang lebih baik tentang fitur inti yang ditawarkan di Maps SDK for Android.

Pelajari lebih lanjut

  • Places SDK for Android—eksplorasi berbagai data tempat untuk menemukan bisnis di sekeliling Anda.
  • android-maps-ktx—sebuah library open source yang memungkinkan Anda terintegrasi dengan Maps SDK for Android dan Library Utilitas Maps SDK for Android dengan cara yang kompatibel dengan Kotlin.
  • android-place-ktx—sebuah library open source yang memungkinkan Anda terintegrasi dengan Places SDK for Android dengan cara yang kompatibel dengan Kotlin.
  • android-sample—kode contoh di GitHub yang menunjukkan semua fitur yang dibahas dalam codelab ini dan lainnya.
  • Codelab Kotlin lainnya untuk membuat aplikasi Android dengan Google Maps Platform