เพิ่มแผนที่ลงในแอป Android (Kotlin)

1. ก่อนที่คุณจะเริ่มต้น

โค้ดแล็บนี้จะสอนวิธีผสานรวม Maps SDK สำหรับ Android กับแอปและใช้ฟีเจอร์หลักของ SDK โดยการสร้างแอปที่แสดงแผนที่ร้านจักรยานในซานฟรานซิสโก รัฐแคลิฟอร์เนีย สหรัฐอเมริกา

f05e1ca27ff42bf6.png

ข้อกำหนดเบื้องต้น

  • ความรู้พื้นฐานเกี่ยวกับการพัฒนา Kotlin และ Android

สิ่งที่คุณต้องดำเนินการ

  • เปิดใช้และใช้ Maps SDK สำหรับ Android เพื่อเพิ่ม Google Maps ลงในแอป Android
  • เพิ่ม ปรับแต่ง และจัดกลุ่มเครื่องหมาย
  • วาดเส้นและรูปหลายเหลี่ยมบนแผนที่
  • ควบคุมมุมมองของกล้องโดยใช้โปรแกรม

สิ่งที่คุณต้องมี

2. ตั้งค่า

สำหรับขั้นตอนการเปิดใช้ต่อไปนี้ คุณต้องเปิดใช้ Maps SDK สำหรับ Android

ตั้งค่า Google Maps Platform

หากยังไม่มีบัญชี Google Cloud Platform และโปรเจ็กต์ที่เปิดใช้การเรียกเก็บเงิน โปรดดูคู่มือเริ่มต้นใช้งาน Google Maps Platform เพื่อสร้างบัญชีสำหรับการเรียกเก็บเงินและโปรเจ็กต์

  1. ใน Cloud Console ให้คลิกเมนูแบบเลื่อนลงของโปรเจ็กต์ แล้วเลือกโปรเจ็กต์ที่ต้องการใช้สำหรับ Codelab นี้

  1. เปิดใช้ Google Maps Platform APIs และ SDK ที่จำเป็นสำหรับ Codelab นี้ใน Google Cloud Marketplace โดยทำตามขั้นตอนในวิดีโอนี้หรือเอกสารประกอบนี้
  2. สร้างคีย์ API ในหน้าข้อมูลเข้าสู่ระบบของ Cloud Console คุณสามารถทำตามขั้นตอนในวิดีโอนี้หรือเอกสารประกอบนี้ คำขอทั้งหมดไปยัง Google Maps Platform ต้องใช้คีย์ API

3. การเริ่มใช้งานอย่างง่าย

เรามีโค้ดเริ่มต้นที่จะช่วยให้คุณเริ่มต้นใช้งานได้อย่างรวดเร็วที่สุด และช่วยให้คุณทำตาม Codelab นี้ได้ คุณสามารถข้ามไปยังโซลูชันได้ แต่หากต้องการทำตามขั้นตอนทั้งหมดเพื่อสร้างโซลูชันด้วยตนเอง โปรดอ่านต่อ

  1. โคลนที่เก็บหากคุณติดตั้ง git ไว้
git clone https://github.com/googlecodelabs/maps-platform-101-android.git

หรือจะคลิกปุ่มต่อไปนี้เพื่อดาวน์โหลดซอร์สโค้ดก็ได้

  1. เมื่อได้รับโค้ดแล้ว ให้เปิดโปรเจ็กต์ที่อยู่ในไดเรกทอรี starter ใน Android Studio

4. เพิ่ม Google Maps

ในส่วนนี้ คุณจะเพิ่ม Google Maps เพื่อให้โหลดเมื่อเปิดแอป

d1d068b5d4ae38b9.png

เพิ่มคีย์ API

คุณต้องระบุคีย์ API ที่สร้างไว้ในขั้นตอนก่อนหน้าให้กับแอป เพื่อให้ Maps SDK สำหรับ Android เชื่อมโยงคีย์กับแอปได้

  1. หากต้องการระบุข้อมูลนี้ ให้เปิดไฟล์ที่ชื่อ local.properties ในไดเรกทอรีรากของโปรเจ็กต์ (ระดับเดียวกับ gradle.properties และ settings.gradle)
  2. ในไฟล์นั้น ให้กำหนดคีย์ใหม่ GOOGLE_MAPS_API_KEY โดยมีค่าเป็นคีย์ API ที่คุณสร้างขึ้น

local.properties

GOOGLE_MAPS_API_KEY=YOUR_KEY_HERE

โปรดทราบว่า local.properties แสดงอยู่ในไฟล์ .gitignore ในที่เก็บ Git เนื่องจากระบบถือว่าคีย์ API เป็นข้อมูลที่ละเอียดอ่อนและไม่ควรเช็คอินไปยังการควบคุมแหล่งที่มา หากเป็นไปได้

  1. จากนั้น หากต้องการเปิดเผย API เพื่อให้ใช้ได้ทั่วทั้งแอป ให้รวมปลั๊กอิน Secrets Gradle Plugin for Android ไว้ในไฟล์ build.gradle ของแอปซึ่งอยู่ในไดเรกทอรี app/ และเพิ่มบรรทัดต่อไปนี้ภายในบล็อก plugins

build.gradle ระดับแอป

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

นอกจากนี้ คุณยังต้องแก้ไขไฟล์ build.gradle ระดับโปรเจ็กต์เพื่อให้มี classpath ต่อไปนี้ด้วย

build.gradle ระดับโปรเจ็กต์

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

ปลั๊กอินนี้จะทำให้คีย์ที่คุณกำหนดไว้ในไฟล์ local.properties พร้อมใช้งานเป็นตัวแปรบิลด์ในไฟล์ Android Manifest และเป็นตัวแปรในคลาส BuildConfig ที่ Gradle สร้างขึ้นในเวลาบิลด์ การใช้ปลั๊กอินนี้จะนำโค้ดบอยเลอร์เพลตที่จำเป็นต่อการอ่านพร็อพเพอร์ตี้จาก local.properties ออก เพื่อให้เข้าถึงได้ทั่วทั้งแอป

เพิ่มการอ้างอิง Google Maps

  1. ตอนนี้คุณเข้าถึงคีย์ API ภายในแอปได้แล้ว ขั้นตอนถัดไปคือการเพิ่มทรัพยากร Dependency ของ Maps SDK สำหรับ Android ลงในไฟล์ build.gradle ของแอป

ในโปรเจ็กต์เริ่มต้นที่มาพร้อมกับโค้ดแล็บนี้ ระบบได้เพิ่มการอ้างอิงนี้ให้คุณแล้ว

build.gradle

dependencies {
   // Dependency to include Maps SDK for Android
   implementation 'com.google.android.gms:play-services-maps:17.0.0'
}
  1. จากนั้นเพิ่มแท็ก meta-data ใหม่ใน AndroidManifest.xml เพื่อส่งคีย์ API ที่คุณสร้างในขั้นตอนก่อนหน้า โดยเปิดไฟล์นี้ใน Android Studio แล้วเพิ่มแท็ก meta-data ต่อไปนี้ภายในออบเจ็กต์ application ในไฟล์ AndroidManifest.xml ซึ่งอยู่ใน app/src/main

AndroidManifest.xml

<meta-data
   android:name="com.google.android.geo.API_KEY"
   android:value="${GOOGLE_MAPS_API_KEY}" />
  1. จากนั้นสร้างไฟล์เลย์เอาต์ใหม่ชื่อ activity_main.xml ในไดเรกทอรี app/src/main/res/layout/ และกำหนดดังนี้

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>

เลย์เอาต์นี้มี FrameLayout เดียวซึ่งมี SupportMapFragment ส่วนนี้มีออบเจ็กต์ GoogleMaps พื้นฐานที่คุณใช้ในขั้นตอนต่อๆ ไป

  1. สุดท้าย ให้อัปเดตคลาส MainActivity ที่อยู่ใน app/src/main/java/com/google/codelabs/buildyourfirstmap โดยเพิ่มโค้ดต่อไปนี้เพื่อลบล้างเมธอด onCreate เพื่อให้คุณตั้งค่าเนื้อหาด้วยเลย์เอาต์ใหม่ที่เพิ่งสร้างได้

MainActivity

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)
}
  1. ตอนนี้ให้เรียกใช้แอป คุณควรเห็นแผนที่โหลดบนหน้าจอของอุปกรณ์

5. การจัดรูปแบบแผนที่ในระบบคลาวด์ (ไม่บังคับ)

คุณปรับแต่งสไตล์ของแผนที่ได้โดยใช้การจัดรูปแบบแผนที่ในระบบคลาวด์

สร้างรหัสแผนที่

หากยังไม่ได้สร้างรหัสแมปที่มีรูปแบบแผนที่เชื่อมโยงอยู่ โปรดดูคำแนะนำเกี่ยวกับรหัสแมปเพื่อทำตามขั้นตอนต่อไปนี้

  1. สร้างรหัสแผนที่
  2. เชื่อมโยงรหัสแผนที่กับรูปแบบแผนที่

การเพิ่มรหัสแมปลงในแอป

หากต้องการใช้รหัสแผนที่ที่สร้างขึ้น ให้แก้ไขไฟล์ activity_main.xml และส่งรหัสแผนที่ในแอตทริบิวต์ map:mapId ของ 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" />

เมื่อทำเสร็จแล้ว ให้เรียกใช้แอปเพื่อดูแผนที่ในสไตล์ที่คุณเลือก

6. เพิ่มเครื่องหมาย

ในงานนี้ คุณจะเพิ่มเครื่องหมายลงในแผนที่ซึ่งแสดงจุดที่น่าสนใจที่ต้องการไฮไลต์บนแผนที่ ก่อนอื่น ให้ดึงรายการสถานที่ที่ระบุไว้ในโปรเจ็กต์เริ่มต้นสำหรับคุณ จากนั้นเพิ่มสถานที่เหล่านั้นลงในแผนที่ ในตัวอย่างนี้คือร้านจักรยาน

bc5576877369b554.png

รับการอ้างอิงไปยัง GoogleMap

ก่อนอื่น คุณต้องรับข้อมูลอ้างอิงไปยังออบเจ็กต์ GoogleMap เพื่อให้คุณใช้วิธีการของออบเจ็กต์ได้ โดยเพิ่มโค้ดต่อไปนี้ในเมธอด MainActivity.onCreate() ทันทีหลังจากเรียกใช้ setContentView()

MainActivity.onCreate()

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

การติดตั้งใช้งานจะค้นหา SupportMapFragment ที่คุณเพิ่มในขั้นตอนก่อนหน้าโดยใช้วิธี findFragmentById() ในออบเจ็กต์ SupportFragmentManager ก่อน เมื่อได้รับข้อมูลอ้างอิงแล้ว ระบบจะเรียกใช้ฟังก์ชัน getMapAsync() ตามด้วยการส่งผ่านใน Lambda Lambda นี้คือที่ที่ส่งผ่านออบเจ็กต์ GoogleMap ภายใน Lambda นี้ ระบบจะเรียกใช้การเรียกเมธอด addMarkers() ซึ่งจะกำหนดในไม่ช้า

ชั้นเรียนที่จัดให้: PlacesReader

ในโปรเจ็กต์เริ่มต้น เราได้จัดเตรียมคลาส PlacesReader ไว้ให้คุณแล้ว คลาสนี้จะอ่านรายการสถานที่ 49 แห่งที่จัดเก็บไว้ในไฟล์ JSON ชื่อ places.json และแสดงผลเป็น List<Place> สถานที่ต่างๆ แสดงรายการร้านจักรยานรอบๆ ซานฟรานซิสโก รัฐแคลิฟอร์เนีย สหรัฐอเมริกา

หากสนใจการใช้งานคลาสนี้ คุณสามารถเข้าถึงได้ใน GitHub หรือเปิดคลาส PlacesReader ใน 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()
       }
   }

โหลดสถานที่

หากต้องการโหลดรายชื่อร้านจักรยาน ให้เพิ่มพร็อพเพอร์ตี้ใน MainActivity ที่ชื่อ places แล้วกำหนดค่าดังนี้

MainActivity.places

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

โค้ดนี้เรียกใช้เมธอด read() ใน PlacesReader ซึ่งจะแสดงผล List<Place> Place มีพร็อพเพอร์ตี้ชื่อ name ซึ่งเป็นชื่อของสถานที่ และ latLng ซึ่งเป็นพิกัดของสถานที่

สถานที่

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

เพิ่มเครื่องหมายลงในแผนที่

เมื่อโหลดรายการสถานที่ลงในหน่วยความจำแล้ว ขั้นตอนถัดไปคือการแสดงสถานที่เหล่านี้บนแผนที่

  1. สร้างเมธอดใน MainActivity ชื่อ addMarkers() และกำหนดเมธอดดังนี้

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

เมธอดนี้จะวนซ้ำในรายการของ places ตามด้วยการเรียกใช้เมธอด addMarker() ในออบเจ็กต์ GoogleMap ที่ระบุ เครื่องหมายจะสร้างขึ้นโดยการสร้างอินสแตนซ์ของออบเจ็กต์ MarkerOptions ซึ่งช่วยให้คุณปรับแต่งเครื่องหมายได้ ในกรณีนี้ ระบบจะระบุชื่อและตำแหน่งของเครื่องหมาย ซึ่งแสดงถึงชื่อร้านจักรยานและพิกัดตามลำดับ

  1. เรียกใช้แอป แล้วไปที่ซานฟรานซิสโกเพื่อดูเครื่องหมายที่คุณเพิ่งเพิ่ม

7. ปรับแต่งเครื่องหมาย

มีตัวเลือกการปรับแต่งหลายอย่างสำหรับเครื่องหมายที่คุณเพิ่งเพิ่มเพื่อช่วยให้เครื่องหมายโดดเด่นและสื่อข้อมูลที่เป็นประโยชน์แก่ผู้ใช้ ในงานนี้ คุณจะได้สำรวจเครื่องหมายบางส่วนโดยการปรับแต่งรูปภาพของเครื่องหมายแต่ละรายการ รวมถึงหน้าต่างข้อมูลที่แสดงเมื่อแตะเครื่องหมาย

a26f82802fe838e9.png

การเพิ่มหน้าต่างข้อมูล

โดยค่าเริ่มต้น หน้าต่างข้อมูลจะแสดงชื่อและข้อมูลโค้ดของเครื่องหมาย (หากตั้งค่าไว้) เมื่อคุณแตะเครื่องหมาย คุณปรับแต่งส่วนนี้เพื่อให้แสดงข้อมูลเพิ่มเติมได้ เช่น ที่อยู่และคะแนนของสถานที่

สร้าง marker_info_contents.xml

ก่อนอื่นให้สร้างไฟล์เลย์เอาต์ใหม่ชื่อ marker_info_contents.xml

  1. โดยคลิกขวาที่โฟลเดอร์ app/src/main/res/layout ในมุมมองโปรเจ็กต์ใน Android Studio แล้วเลือกใหม่ > ไฟล์ทรัพยากรเลย์เอาต์

8cac51fcbef9171b.png

  1. ในกล่องโต้ตอบ ให้พิมพ์ marker_info_contents ในช่องชื่อไฟล์ และ LinearLayout ในช่อง Root element จากนั้นคลิกตกลง

8783af12baf07a80.png

ต่อมาจะขยายไฟล์เลย์เอาต์นี้เพื่อแสดงเนื้อหาภายในหน้าต่างข้อมูล

  1. คัดลอกเนื้อหาในข้อมูลโค้ดต่อไปนี้ ซึ่งจะเพิ่ม TextViews 3 รายการภายในกลุ่มมุมมองแนวตั้ง LinearLayout แล้วเขียนทับโค้ดเริ่มต้นในไฟล์

marker_info_contents.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:orientation="vertical"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:gravity="center_horizontal"
   android:padding="8dp">

   <TextView
       android:id="@+id/text_view_title"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textColor="@android:color/black"
       android:textSize="18sp"
       android:textStyle="bold"
       tools:text="Title"/>

   <TextView
       android:id="@+id/text_view_address"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textColor="@android:color/black"
       android:textSize="16sp"
       tools:text="123 Main Street"/>

   <TextView
       android:id="@+id/text_view_rating"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textColor="@android:color/black"
       android:textSize="16sp"
       tools:text="Rating: 3"/>

</LinearLayout>

สร้างการใช้งาน InfoWindowAdapter

หลังจากสร้างไฟล์เลย์เอาต์สำหรับหน้าต่างข้อมูลที่กำหนดเองแล้ว ขั้นตอนถัดไปคือการใช้ส่วนติดต่อ GoogleMap.InfoWindowAdapter อินเทอร์เฟซนี้มี 2 วิธี ได้แก่ getInfoWindow() และ getInfoContents() ทั้ง 2 วิธีจะแสดงผลออบเจ็กต์ View ที่ไม่บังคับ โดยวิธีแรกใช้เพื่อปรับแต่งหน้าต่างเอง ส่วนวิธีที่ 2 ใช้เพื่อปรับแต่งเนื้อหาของหน้าต่าง ในกรณีของคุณ คุณจะใช้ทั้ง 2 อย่างและปรับแต่งการคืนค่าของ getInfoContents() ขณะที่คืนค่าเป็น Null ใน getInfoWindow() ซึ่งบ่งบอกว่าควรใช้ช่วงเริ่มต้น

  1. สร้างไฟล์ Kotlin ใหม่ชื่อ MarkerInfoWindowAdapter ในแพ็กเกจเดียวกับ MainActivity โดยคลิกขวาที่โฟลเดอร์ app/src/main/java/com/google/codelabs/buildyourfirstmap ในมุมมองโปรเจ็กต์ใน Android Studio จากนั้นเลือกใหม่ > ไฟล์/คลาส Kotlin

3975ba36eba9f8e1.png

  1. ในกล่องโต้ตอบ ให้พิมพ์ MarkerInfoWindowAdapter และไฮไลต์ไฟล์ไว้

992235af53d3897f.png

  1. เมื่อสร้างไฟล์แล้ว ให้คัดลอกเนื้อหาในข้อมูลโค้ดต่อไปนี้ลงในไฟล์ใหม่

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

ในเนื้อหาของเมธอด getInfoContents() ระบบจะแคสต์เครื่องหมายที่ระบุในเมธอดเป็นประเภท Place และหากแคสต์ไม่ได้ เมธอดจะแสดงผลเป็น Null (คุณยังไม่ได้ตั้งค่าพร็อพเพอร์ตี้แท็กใน Marker แต่คุณจะทำในขั้นตอนถัดไป)

จากนั้นจะมีการขยายเลย์เอาต์ marker_info_contents.xml ตามด้วยการตั้งค่าข้อความใน TextViews ที่มีแท็ก Place

อัปเดต MainActivity

หากต้องการเชื่อมต่อคอมโพเนนต์ทั้งหมดที่คุณสร้างไว้จนถึงตอนนี้ คุณต้องเพิ่ม 2 บรรทัดในคลาส MainActivity

ก่อนอื่น หากต้องการส่ง InfoWindowAdapter, MarkerInfoWindowAdapter ที่กำหนดเองภายในการเรียกเมธอด getMapAsync ให้เรียกใช้เมธอด setInfoWindowAdapter() ในออบเจ็กต์ GoogleMap และสร้างอินสแตนซ์ใหม่ของ MarkerInfoWindowAdapter

  1. โดยเพิ่มโค้ดต่อไปนี้หลังการเรียกใช้เมธอด addMarkers() ภายใน Lambda getMapAsync()

MainActivity.onCreate()

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

สุดท้าย คุณจะต้องตั้งค่าสถานที่แต่ละแห่งเป็นพร็อพเพอร์ตี้แท็กในทุกเครื่องหมายที่เพิ่มลงในแผนที่

  1. โดยแก้ไขplaces.forEach{}การเรียกใช้ในฟังก์ชัน addMarkers() ด้วยข้อมูลต่อไปนี้

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
}

เพิ่มรูปภาพเครื่องหมายที่กำหนดเอง

การปรับแต่งรูปภาพเครื่องหมายเป็นวิธีที่สนุกในการสื่อสารประเภทสถานที่ที่เครื่องหมายแสดงบนแผนที่ ในขั้นตอนนี้ คุณจะแสดงจักรยานแทนเครื่องหมายสีแดงเริ่มต้นเพื่อแสดงร้านค้าแต่ละร้านบนแผนที่ โปรเจ็กต์เริ่มต้นมีไอคอนจักรยาน ic_directions_bike_black_24dp.xml ใน app/src/res/drawable ซึ่งคุณใช้

6eb7358bb61b0a88.png

ตั้งค่าบิตแมปที่กำหนดเองในเครื่องหมาย

เมื่อมีไอคอนจักรยานแบบ Vector Drawable แล้ว ขั้นตอนถัดไปคือการตั้งค่า Drawable นั้นเป็นไอคอนของเครื่องหมายแต่ละรายการบนแผนที่ MarkerOptions มีเมธอด icon ซึ่งรับ BitmapDescriptor ที่คุณใช้เพื่อดำเนินการนี้

ก่อนอื่น คุณต้องแปลง Vector Drawable ที่เพิ่งเพิ่มเป็น BitmapDescriptor ไฟล์ชื่อ BitMapHelper ที่รวมอยู่ในโปรเจ็กต์เริ่มต้นมีฟังก์ชันช่วยที่ชื่อ vectorToBitmap() ซึ่งทำหน้าที่ดังกล่าว

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

เมธอดนี้รับ Context, รหัสทรัพยากรที่วาดได้ รวมถึงจำนวนเต็มสี และสร้างการแสดง BitmapDescriptor ของทรัพยากรนั้น

เมื่อใช้วิธีการช่วย ให้ประกาศพร็อพเพอร์ตี้ใหม่ชื่อ bicycleIcon และกำหนดคำจำกัดความต่อไปนี้ 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)
}

พร็อพเพอร์ตี้นี้ใช้สี colorPrimary ที่กำหนดไว้ล่วงหน้าในแอป และใช้สีดังกล่าวเพื่อปรับสีไอคอนจักรยานและส่งกลับเป็น BitmapDescriptor

  1. เมื่อใช้พร็อพเพอร์ตี้นี้ ให้เรียกใช้เมธอด icon ของ MarkerOptions ในเมธอด addMarkers() เพื่อปรับแต่งไอคอนให้เสร็จสมบูรณ์ เมื่อทำเช่นนี้ พร็อพเพอร์ตี้เครื่องหมายควรมีลักษณะดังนี้

MainActivity.addMarkers()

val marker = googleMap.addMarker(
    MarkerOptions()
        .title(place.name)
        .position(place.latLng)
        .icon(bicycleIcon)
)
  1. เรียกใช้แอปเพื่อดูเครื่องหมายที่อัปเดตแล้ว

8. เครื่องหมายคลัสเตอร์

คุณอาจสังเกตเห็นว่าเครื่องหมายที่คุณเพิ่มทับซ้อนกัน ทั้งนี้ขึ้นอยู่กับระดับการซูมแผนที่ เครื่องหมายที่ทับซ้อนกันโต้ตอบได้ยากมากและสร้างสัญญาณรบกวนจำนวนมาก ซึ่งส่งผลต่อความสามารถในการใช้งานของแอป

68591edc86d73724.png

แนวทางปฏิบัติแนะนำในการปรับปรุงประสบการณ์ของผู้ใช้ในกรณีนี้คือ เมื่อใดก็ตามที่คุณมีชุดข้อมูลขนาดใหญ่ที่จัดกลุ่มอย่างใกล้ชิด ให้ใช้การจัดกลุ่มเครื่องหมาย เมื่อใช้การจัดกลุ่ม ขณะที่คุณซูมเข้าและออกในแผนที่ เครื่องหมายที่อยู่ใกล้กันจะได้รับการจัดกลุ่มเข้าด้วยกันดังนี้

f05e1ca27ff42bf6.png

หากต้องการติดตั้งใช้งานฟีเจอร์นี้ คุณต้องได้รับความช่วยเหลือจากไลบรารียูทิลิตี Maps SDK สำหรับ Android

ไลบรารียูทิลิตี Maps SDK สำหรับ Android

ไลบรารียูทิลิตีของ Maps SDK สำหรับ Android สร้างขึ้นเพื่อเป็นวิธีขยายฟังก์ชันการทำงานของ Maps SDK สำหรับ Android โดยมีฟีเจอร์ขั้นสูง เช่น การจัดกลุ่มเครื่องหมาย แผนที่ความร้อน การรองรับ KML และ GeoJson การเข้ารหัสและถอดรหัสเส้นหลายเส้น รวมถึงฟังก์ชันตัวช่วยเล็กๆ น้อยๆ เกี่ยวกับรูปทรงเรขาคณิตทรงกลม

อัปเดต build.gradle

เนื่องจากไลบรารียูทิลิตีได้รับการแพ็กเกจแยกจาก Maps SDK สำหรับ Android คุณจึงต้องเพิ่มทรัพยากร Dependency เพิ่มเติมลงในไฟล์ build.gradle

  1. โปรดอัปเดตส่วน dependencies ของไฟล์ app/build.gradle

build.gradle

implementation 'com.google.maps.android:android-maps-utils:1.1.0'
  1. เมื่อเพิ่มบรรทัดนี้แล้ว คุณต้องซิงค์โปรเจ็กต์เพื่อดึงข้อมูลการอ้างอิงใหม่

b7b030ec82c007fd.png

ใช้การจัดกลุ่ม

หากต้องการใช้การจัดกลุ่มในแอป ให้ทำตาม 3 ขั้นตอนต่อไปนี้

  1. ใช้ClusterItemอินเทอร์เฟซ
  2. สร้างคลาสย่อยของคลาส DefaultClusterRenderer
  3. สร้าง ClusterManager แล้วเพิ่มรายการ

ใช้ ClusterItem Interface

ออบเจ็กต์ทั้งหมดที่แสดงเครื่องหมายที่จัดกลุ่มได้บนแผนที่ต้องใช้ClusterItemอินเทอร์เฟซ ในกรณีของคุณ นั่นหมายความว่าPlaceโมเดลต้องเป็นไปตามClusterItem เปิดไฟล์ Place.kt แล้วทำการแก้ไขต่อไปนี้

สถานที่

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 จะกำหนด 3 วิธีต่อไปนี้

  • getPosition() ซึ่งแสดงถึงLatLngของสถานที่
  • getTitle() ซึ่งแสดงชื่อของสถานที่
  • getSnippet() ซึ่งแสดงถึงที่อยู่ของสถานที่

สร้างคลาสย่อยของคลาส DefaultClusterRenderer

คลาสที่รับผิดชอบในการใช้การจัดกลุ่ม ClusterManager ภายในใช้คลาส ClusterRenderer เพื่อจัดการการสร้างคลัสเตอร์ขณะที่คุณเลื่อนและซูมแผนที่ โดยค่าเริ่มต้นแล้ว จะมาพร้อมกับตัวแสดงผลเริ่มต้น DefaultClusterRenderer ซึ่งใช้ ClusterRenderer สำหรับกรณีที่ซับซ้อนน้อย การดำเนินการนี้ควรเพียงพอ แต่ในกรณีของคุณ เนื่องจากต้องปรับแต่งเครื่องหมาย คุณจึงต้องขยายคลาสนี้และเพิ่มการปรับแต่งในนั้น

สร้างไฟล์ Kotlin PlaceRenderer.kt ในแพ็กเกจ com.google.codelabs.buildyourfirstmap.place แล้วกำหนดค่าดังนี้

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

คลาสนี้จะลบล้างฟังก์ชัน 2 อย่างต่อไปนี้

  • onBeforeClusterItemRendered() ซึ่งจะเรียกใช้ก่อนแสดงคลัสเตอร์บนแผนที่ ในที่นี้ คุณสามารถปรับแต่งผ่าน MarkerOptions ได้ ในกรณีนี้คือการตั้งค่าชื่อ ตำแหน่ง และไอคอนของเครื่องหมาย
  • onClusterItemRenderer() ซึ่งจะเรียกใช้ทันทีหลังจากที่แสดงเครื่องหมายบนแผนที่ คุณสามารถเข้าถึงMarkerออบเจ็กต์ที่สร้างขึ้นได้ที่นี่ ในกรณีนี้ ออบเจ็กต์จะตั้งค่าพร็อพเพอร์ตี้แท็กของเครื่องหมาย

สร้าง ClusterManager และเพิ่มรายการ

สุดท้ายนี้ หากต้องการให้การจัดกลุ่มทำงานได้ คุณต้องแก้ไข MainActivity เพื่อสร้างอินสแตนซ์ ClusterManager และระบุการอ้างอิงที่จำเป็น ClusterManager จะจัดการการเพิ่มเครื่องหมาย (ออบเจ็กต์ ClusterItem) ภายใน ดังนั้นแทนที่จะเพิ่มเครื่องหมายลงในแผนที่โดยตรง ความรับผิดชอบนี้จะมอบหมายให้ ClusterManager นอกจากนี้ ClusterManager ยังเรียกใช้ setInfoWindowAdapter() ภายในด้วย ดังนั้นการตั้งค่าหน้าต่างข้อมูลที่กำหนดเองจะต้องทำในออบเจ็กต์ MarkerManager.Collection ของ ClusterManger

  1. หากต้องการเริ่มต้น ให้แก้ไขเนื้อหาของ Lambda ในgetMapAsync()การเรียกใช้ใน MainActivity.onCreate() แสดงความคิดเห็นในส่วนที่เรียกใช้ addMarkers() และ setInfoWindowAdapter() แล้วเรียกใช้เมธอดที่ชื่อ addClusteredMarkers() แทน ซึ่งคุณจะกำหนดในขั้นตอนถัดไป

MainActivity.onCreate()

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

    // Set custom info window adapter.
    // googleMap.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))
}
  1. ถัดไป ใน 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()
   }
}

เมธอดนี้จะสร้างอินสแตนซ์ของ ClusterManager ส่งเครื่องมือแสดงผลที่กำหนดเอง PlacesRenderer ไปยังอินสแตนซ์ดังกล่าว เพิ่มสถานที่ทั้งหมด และเรียกใช้เมธอด cluster() นอกจากนี้ เนื่องจาก ClusterManager ใช้เมธอด setInfoWindowAdapter() ในออบเจ็กต์แผนที่ การตั้งค่าหน้าต่างข้อมูลที่กำหนดเองจึงต้องทำในออบเจ็กต์ ClusterManager.markerCollection สุดท้ายนี้ เนื่องจากคุณต้องการให้การจัดกลุ่มเปลี่ยนแปลงเมื่อผู้ใช้เลื่อนและซูมแผนที่ จึงมีการระบุ OnCameraIdleListener ให้กับ googleMap เพื่อให้เมื่อกล้องไม่ได้ใช้งาน ระบบจะเรียกใช้ clusterManager.onCameraIdle()

  1. ไปที่แอปเพื่อดูร้านค้าที่จัดกลุ่มใหม่กันเลย

9. วาดในแผนที่

แม้ว่าคุณจะเคยสำรวจวิธีวาดบนแผนที่ (โดยการเพิ่มเครื่องหมาย) มาแล้ว แต่ Maps SDK สำหรับ Android ก็รองรับวิธีอื่นๆ อีกมากมายที่คุณสามารถใช้วาดเพื่อแสดงข้อมูลที่เป็นประโยชน์บนแผนที่

เช่น หากต้องการแสดงเส้นทางและพื้นที่บนแผนที่ คุณสามารถใช้เส้นหลายเส้นและรูปหลายเหลี่ยมเพื่อแสดงข้อมูลเหล่านี้บนแผนที่ได้ หรือหากต้องการยึดรูปภาพไว้กับพื้นผิว คุณก็ใช้ภาพซ้อนทับบนพื้นได้

ในงานนี้ คุณจะได้เรียนรู้วิธีวาดรูปร่าง โดยเฉพาะวงกลมรอบเครื่องหมายทุกครั้งที่มีการแตะ

f98ce13055430352.png

เพิ่ม Listener การคลิก

โดยปกติแล้ว วิธีเพิ่ม Listener การคลิกไปยังเครื่องหมายคือการส่ง Listener การคลิกไปยังออบเจ็กต์ GoogleMap โดยตรงผ่าน setOnMarkerClickListener() อย่างไรก็ตาม เนื่องจากคุณใช้การจัดกลุ่ม จึงต้องระบุเครื่องมือฟังการคลิกให้กับ ClusterManager แทน

  1. ในaddClusteredMarkers()วิธีในMainActivity ให้เพิ่มบรรทัดต่อไปนี้หลังการเรียกใช้ไปยัง cluster()

MainActivity.addClusteredMarkers()

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

วิธีการนี้จะเพิ่ม Listener และเรียกใช้เมธอด addCircle() ซึ่งคุณจะกำหนดในขั้นตอนถัดไป สุดท้ายนี้ ระบบจะส่งคืน false จากเมธอดนี้เพื่อระบุว่าเมธอดนี้ไม่ได้ใช้เหตุการณ์นี้

  1. จากนั้น คุณต้องกำหนดพร็อพเพอร์ตี้ circle และเมธอด addCircle() ใน 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))
   )
}

ระบบจะตั้งค่าcircleพร็อพเพอร์ตี้เพื่อให้เมื่อใดก็ตามที่มีการแตะเครื่องหมายใหม่ ระบบจะนำวงกลมก่อนหน้าออกและเพิ่มวงกลมใหม่ โปรดสังเกตว่า API สำหรับการเพิ่มวงกลมนั้นค่อนข้างคล้ายกับการเพิ่มเครื่องหมาย

  1. ตอนนี้ให้ไปเรียกใช้แอปเพื่อดูการเปลี่ยนแปลง

10. การควบคุมกล้อง

งานสุดท้ายคือการดูการควบคุมกล้องเพื่อให้คุณโฟกัสมุมมองรอบๆ ภูมิภาคหนึ่งๆ ได้

กล้องและมุมมอง

หากคุณสังเกตเห็นเมื่อเรียกใช้แอป กล้องจะแสดงทวีปแอฟริกา และคุณต้องเลื่อนและซูมอย่างละเอียดไปยังซานฟรานซิสโกเพื่อค้นหาเครื่องหมายที่คุณเพิ่ม แม้ว่าจะเป็นวิธีที่สนุกในการสำรวจโลก แต่ก็ไม่เป็นประโยชน์หากคุณต้องการแสดงเครื่องหมายทันที

คุณสามารถตั้งค่าตำแหน่งของกล้องแบบเป็นโปรแกรมเพื่อให้มุมมองอยู่ตรงกลางในตำแหน่งที่ต้องการได้

  1. ไปที่การเรียก getMapAsync() แล้วเพิ่มโค้ดต่อไปนี้เพื่อปรับมุมมองกล้องให้เริ่มต้นที่ซานฟรานซิสโกเมื่อเปิดแอป

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

ก่อนอื่น เราจะเรียกใช้ setOnMapLoadedCallback() เพื่อให้ระบบอัปเดตกล้องหลังจากโหลดแผนที่แล้วเท่านั้น ขั้นตอนนี้จำเป็นเนื่องจากต้องคำนวณพร็อพเพอร์ตี้ของแผนที่ เช่น ขนาด ก่อนที่จะทำการเรียกอัปเดตกล้อง

ใน Lambda จะมีการสร้างออบเจ็กต์ LatLngBounds ใหม่ ซึ่งกำหนดภูมิภาคสี่เหลี่ยมผืนผ้าบนแผนที่ โดยจะสร้างขึ้นทีละส่วนด้วยการรวมค่า LatLng ของสถานที่ทั้งหมดไว้ในนั้นเพื่อให้แน่ใจว่าสถานที่ทั้งหมดอยู่ภายในขอบเขต เมื่อสร้างออบเจ็กต์นี้แล้ว ระบบจะเรียกใช้เมธอด moveCamera() ใน GoogleMap และส่ง CameraUpdate ไปให้ผ่าน CameraUpdateFactory.newLatLngBounds(bounds.build(), 20)

  1. เรียกใช้แอปและสังเกตว่าตอนนี้กล้องเริ่มต้นใช้งานในซานฟรานซิสโกแล้ว

การฟังการเปลี่ยนแปลงของกล้อง

นอกจากจะปรับเปลี่ยนตำแหน่งกล้องแล้ว คุณยังฟังข้อมูลอัปเดตกล้องขณะที่ผู้ใช้เคลื่อนที่ไปรอบๆ แผนที่ได้ด้วย ซึ่งอาจมีประโยชน์หากคุณต้องการแก้ไข UI ขณะที่กล้องเคลื่อนที่

คุณแก้ไขโค้ดเพื่อทำให้เครื่องหมายโปร่งแสงทุกครั้งที่ย้ายกล้องได้เพื่อความสนุก

  1. ในaddClusteredMarkers() ให้เพิ่มบรรทัดต่อไปนี้ที่ด้านล่างของเมธอด

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

ซึ่งจะเพิ่ม OnCameraMoveStartedListener เพื่อให้เมื่อใดก็ตามที่กล้องเริ่มเคลื่อนไหว ระบบจะแก้ไขค่าอัลฟ่าของเครื่องหมายทั้งหมด (ทั้งคลัสเตอร์และเครื่องหมาย) เป็น 0.3f เพื่อให้เครื่องหมายปรากฏเป็นแบบโปร่งแสง

  1. สุดท้ายนี้ หากต้องการแก้ไขเครื่องหมายกึ่งโปร่งแสงให้กลับมาเป็นทึบแสงเมื่อกล้องหยุด ให้แก้ไขเนื้อหาของ setOnCameraIdleListener ในเมธอด addClusteredMarkers() เป็นดังนี้

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. เรียกใช้แอปเพื่อดูผลลัพธ์ได้เลย

11. Maps KTX

สำหรับแอป Kotlin ที่ใช้ Google Maps Platform Android SDK อย่างน้อย 1 รายการ ไลบรารีส่วนขยาย Kotlin หรือ KTX จะพร้อมใช้งานเพื่อให้คุณใช้ประโยชน์จากฟีเจอร์ภาษา Kotlin เช่น Coroutine, พร็อพเพอร์ตี้/ฟังก์ชันส่วนขยาย และอื่นๆ ได้ Google Maps SDK แต่ละรายการมีไลบรารี KTX ที่เกี่ยวข้องดังที่แสดงด้านล่าง

แผนภาพ KTX ของ Google Maps Platform

ในงานนี้ คุณจะได้ใช้ไลบรารี Maps KTX และ Maps Utils KTX กับแอป และปรับโครงสร้างการใช้งานของงานก่อนหน้าเพื่อให้ใช้ฟีเจอร์ภาษาเฉพาะของ Kotlin ในแอปได้

  1. รวมทรัพยากร Dependency KTX ในไฟล์ build.gradle ระดับแอป

เนื่องจากแอปใช้ทั้ง Maps SDK สำหรับ Android และไลบรารียูทิลิตี Maps SDK สำหรับ Android คุณจึงต้องรวมไลบรารี KTX ที่เกี่ยวข้องสำหรับไลบรารีเหล่านี้ นอกจากนี้ คุณยังจะได้ใช้ฟีเจอร์ที่อยู่ในไลบรารี KTX ของ AndroidX Lifecycle ในงานนี้ด้วย ดังนั้นให้รวมทรัพยากร Dependency นั้นไว้ในไฟล์ build.gradle ระดับแอปด้วย

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. ใช้ฟังก์ชันส่วนขยาย GoogleMap.addMarker() และ GoogleMap.addCircle()

ไลบรารี Maps KTX มี API ทางเลือกในรูปแบบ DSL สำหรับ GoogleMap.addMarker(MarkerOptions) และ GoogleMap.addCircle(CircleOptions) ที่ใช้ในขั้นตอนก่อนหน้า หากต้องการใช้ API ที่กล่าวถึงข้างต้น คุณต้องสร้างคลาสที่มีตัวเลือกสำหรับเครื่องหมายหรือวงกลม ในขณะที่ตัวเลือก KTX ช่วยให้คุณตั้งค่าเครื่องหมายหรือวงกลมใน Lambda ที่คุณระบุได้

หากต้องการใช้ API เหล่านี้ ให้อัปเดตเมธอด MainActivity.addMarkers(GoogleMap) และ 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))
    }
}

การเขียนเมธอดข้างต้นใหม่ในลักษณะนี้จะอ่านได้กระชับกว่ามาก ซึ่งทำได้โดยใช้ฟังก์ชันลิเทอรัลที่มีตัวรับของ Kotlin

  1. ใช้ฟังก์ชันส่วนขยายที่ระงับ SupportMapFragment.awaitMap() และ GoogleMap.awaitMapLoad()

นอกจากนี้ ไลบรารี Maps KTX ยังมีส่วนขยายฟังก์ชันที่ระงับการทำงานเพื่อใช้ภายในโครูทีนด้วย โดยเฉพาะอย่างยิ่ง มีฟังก์ชันการระงับทางเลือกสำหรับ SupportMapFragment.getMapAsync(OnMapReadyCallback) และ GoogleMap.setOnMapLoadedCallback(OnMapLoadedCallback) การใช้ API ทางเลือกเหล่านี้จะช่วยให้คุณไม่ต้องส่งการเรียกกลับ และช่วยให้คุณรับการตอบกลับของเมธอดเหล่านี้ในลักษณะอนุกรมและแบบซิงโครนัสได้แทน

เนื่องจากเมธอดเหล่านี้เป็นฟังก์ชันที่ระงับ การใช้งานจึงต้องเกิดขึ้นภายในโครูทีน ไลบรารี Lifecycle Runtime KTX มีส่วนขยายเพื่อจัดเตรียมขอบเขตของโครูทีนที่รับรู้ถึงวงจรของกิจกรรม เพื่อให้โครูทีนทำงานและหยุดทำงานในเหตุการณ์วงจรของกิจกรรมที่เหมาะสม

เมื่อรวมแนวคิดเหล่านี้แล้ว ให้อัปเดตเมธอด MainActivity.onCreate(Bundle) ดังนี้

MainActivity.onCreate(Bundle)

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val mapFragment =
        supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment
    lifecycleScope.launchWhenCreated {
        // Get map
        val googleMap = mapFragment.awaitMap()

        // Wait for map to finish loading
        googleMap.awaitMapLoad()

        // Ensure all places are visible in the map
        val bounds = LatLngBounds.builder()
        places.forEach { bounds.include(it.latLng) }
        googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), 20))

        addClusteredMarkers(googleMap)
    }
}

lifecycleScope.launchWhenCreatedขอบเขตของโครูทีนจะเรียกใช้บล็อกเมื่อกิจกรรมอยู่ในสถานะสร้างเป็นอย่างน้อย นอกจากนี้ โปรดสังเกตว่าการเรียกเพื่อดึงข้อมูลออบเจ็กต์ GoogleMap และการรอให้แผนที่โหลดเสร็จได้ถูกแทนที่ด้วย SupportMapFragment.awaitMap() และ GoogleMap.awaitMapLoad() ตามลำดับ การปรับโครงสร้างโค้ดโดยใช้ฟังก์ชันระงับเหล่านี้ช่วยให้คุณเขียนโค้ดที่เทียบเท่าซึ่งอิงตามการเรียกกลับในลักษณะลำดับได้

  1. ตอนนี้คุณก็สร้างแอปใหม่โดยใช้การเปลี่ยนแปลงที่รีแฟกเตอร์แล้วได้เลย

12. ขอแสดงความยินดี

ยินดีด้วย คุณได้เรียนรู้เนื้อหามากมาย และเราหวังว่าคุณจะเข้าใจฟีเจอร์หลักที่อยู่ใน Maps SDK สำหรับ Android ได้ดียิ่งขึ้น

ดูข้อมูลเพิ่มเติม

  • Places SDK สำหรับ Android - สำรวจชุดข้อมูลสถานที่ที่ครอบคลุมเพื่อค้นหาธุรกิจรอบตัวคุณ
  • android-maps-ktx - ไลบรารีโอเพนซอร์สที่ช่วยให้คุณผสานรวมกับ Maps SDK สำหรับ Android และ Maps SDK สำหรับ Android Utility Library ในลักษณะที่เป็นมิตรกับ Kotlin
  • android-place-ktx - ไลบรารีโอเพนซอร์สที่ช่วยให้คุณผสานรวมกับ Places SDK สำหรับ Android ในลักษณะที่เป็นมิตรกับ Kotlin
  • android-samples - โค้ดตัวอย่างใน GitHub ที่แสดงฟีเจอร์ทั้งหมดที่กล่าวถึงในโค้ดแล็บนี้และอื่นๆ
  • Codelab ของ Kotlin เพิ่มเติมสำหรับการสร้างแอป Android ด้วย Google Maps Platform