1. Trước khi bắt đầu
Lớp học lập trình này hướng dẫn bạn cách tích hợp SDK Maps dành cho Android vào ứng dụng của bạn và sử dụng các tính năng cốt lõi của ứng dụng này bằng cách tạo một ứng dụng hiển thị bản đồ các cửa hàng xe đạp ở San Francisco, CA, Hoa Kỳ.
Điều kiện tiên quyết
- Có kiến thức cơ bản về Kotlin và phát triển Android
Bạn sẽ thực hiện
- Bật và sử dụng SDK Maps dành cho Android để thêm Google Maps vào ứng dụng Android.
- Thêm, tùy chỉnh và nhóm các điểm đánh dấu.
- Vẽ nhiều đường kẻ và đa giác trên bản đồ.
- Điều khiển góc nhìn của camera bằng cách lập trình.
Bạn cần có
- SDK Maps dành cho Android
- Tài khoản Google có bật tính năng thanh toán
- Android Studio 2020.3.1 trở lên
- Dịch vụ Google Play được cài đặt trong Android Studio
- Một thiết bị Android hoặc trình mô phỏng Android chạy nền tảng API của Google dựa trên Android 4.2.2 trở lên (xem phần Chạy ứng dụng trên Trình mô phỏng Android để biết các bước cài đặt.)
2. Bắt đầu thiết lập
Đối với bước bật sau đây , bạn cần bật SDK Maps cho Android.
Thiết lập Nền tảng Google Maps
Nếu bạn chưa có tài khoản Google Cloud Platform và một dự án đã bật tính năng thanh toán, vui lòng xem hướng dẫn Bắt đầu sử dụng Google Maps Platform để tạo tài khoản thanh toán và một dự án.
- Trong Cloud Console, hãy nhấp vào trình đơn thả xuống dự án và chọn dự án mà bạn muốn sử dụng cho lớp học lập trình này.
- Bật API và SDK của Nền tảng Google Maps bắt buộc cho lớp học lập trình này trong Google Cloud Marketplace. Để làm như vậy, hãy làm theo các bước trong video này hoặc tài liệu này.
- Tạo khoá API trong trang Thông tin xác thực của Cloud Console. Bạn có thể làm theo các bước trong video này hoặc tài liệu này. Tất cả các yêu cầu gửi đến Google Maps Platform đều yêu cầu khóa API.
3. Bắt đầu nhanh
Để bắt đầu nhanh nhất có thể, hãy tham khảo khóa học lập trình này để tham khảo một số mã dành cho người mới bắt đầu. Bạn có thể chuyển sang giải pháp này, nhưng nếu muốn làm theo tất cả các bước để tự xây dựng giải pháp, hãy đọc tiếp.
- Sao chép kho lưu trữ nếu bạn đã cài đặt
git
.
git clone https://github.com/googlecodelabs/maps-platform-101-android.git
Ngoài ra, bạn có thể nhấp vào nút sau để tải mã nguồn xuống.
- Sau khi nhận được mã, hãy tiếp tục và mở dự án trong thư mục
starter
trong Android Studio.
4. Thêm Google Maps
Trong phần này, bạn sẽ thêm Google Maps để nó tải khi bạn khởi chạy ứng dụng.
Thêm khóa API của bạn
Bạn cần cung cấp khóa API mà bạn đã tạo ở bước trước cho ứng dụng để SDK Maps dành cho Android có thể liên kết khóa của bạn với ứng dụng.
- Để cung cấp mục này, hãy mở tệp có tên
local.properties
trong thư mục gốc của dự án (cùng cấp vớigradle.properties
vàsettings.gradle
). - Trong tệp đó, hãy xác định khoá mới
GOOGLE_MAPS_API_KEY
có giá trị là khoá API mà bạn đã tạo.
local.properties
GOOGLE_MAPS_API_KEY=YOUR_KEY_HERE
Xin lưu ý rằng local.properties
được liệt kê trong tệp .gitignore
trong kho lưu trữ Git. Điều này là do khóa API của bạn được coi là thông tin nhạy cảm và không nên kiểm tra nguồn kiểm soát nguồn, nếu có thể.
- Tiếp theo, để hiển thị API của bạn để có thể sử dụng trên toàn ứng dụng, hãy thêm trình bổ trợ Bí mật Gradle cho Android vào tệp
build.gradle
của ứng dụng nằm trong thư mụcapp/
và thêm dòng sau vào khốiplugins
:
build.gradle cấp ứng dụng
plugins {
// ...
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
}
Bạn cũng sẽ cần sửa đổi tệp build.gradle
cấp dự án để thêm đường dẫn lớp sau:
build.gradle cấp dự án
buildscript {
dependencies {
// ...
classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:1.3.0"
}
}
Trình bổ trợ này sẽ cung cấp các khóa bạn đã xác định trong tệp local.properties
dưới dạng biến bản dựng trong tệp kê khai Android và dưới dạng biến trong lớp BuildConfig
do Gradle tạo vào thời điểm tạo. Việc sử dụng trình bổ trợ này sẽ xóa mã nguyên mẫu mà sẽ cần thiết để đọc các thuộc tính khỏi local.properties
để có thể truy cập mã trong suốt ứng dụng của bạn.
Thêm phần phụ thuộc vào Google Maps
- Bây giờ, bạn có thể truy cập vào khóa API bên trong ứng dụng, bước tiếp theo là thêm SDK Maps dành cho Android vào tệp
build.gradle
của ứng dụng.
Trong dự án dành cho người mới bắt đầu đi kèm với lớp học lập trình này, phần phụ thuộc này đã được thêm vào cho bạn.
build.gradle
dependencies {
// Dependency to include Maps SDK for Android
implementation 'com.google.android.gms:play-services-maps:17.0.0'
}
- Tiếp theo, hãy thêm thẻ
meta-data
mới trongAndroidManifest.xml
để chuyển vào khóa API mà bạn đã tạo ở bước trước đó. Để thực hiện việc này, hãy tiếp tục và mở tệp này trong Android Studio rồi thêm thẻmeta-data
sau vào trong đối tượngapplication
trong tệpAndroidManifest.xml
của bạn, nằm trongapp/src/main
.
AndroidManifest.xml
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${GOOGLE_MAPS_API_KEY}" />
- Tiếp theo, hãy tạo tệp bố cục mới có tên là
activity_main.xml
trong thư mụcapp/src/main/res/layout/
và xác định tệp như sau:
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>
Bố cục này có một FrameLayout
duy nhất chứa SupportMapFragment
. Mảnh này chứa đối tượng GoogleMaps
cơ bản mà bạn sử dụng trong các bước sau này.
- Cuối cùng, hãy cập nhật lớp
MainActivity
nằm trongapp/src/main/java/com/google/codelabs/buildyourfirstmap
bằng cách thêm mã sau để ghi đè phương thứconCreate
, nhờ đó, bạn có thể đặt nội dung của phương thức bằng bố cục mới mà bạn vừa tạo.
Hoạt động chính
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
- Bây giờ, hãy tiếp tục và chạy ứng dụng. Bây giờ, bạn sẽ thấy tải bản đồ trên màn hình của thiết bị.
5. Tạo bản đồ dựa trên đám mây (Không bắt buộc)
Bạn có thể tùy chỉnh kiểu của bản đồ bằng cách tạo kiểu bản đồ dựa trên đám mây.
Tạo mã bản đồ
Nếu bạn chưa tạo mã bản đồ với kiểu bản đồ được liên kết, hãy xem hướng dẫn Mã bản đồ để hoàn tất các bước sau:
- Tạo mã bản đồ.
- Liên kết mã bản đồ với kiểu bản đồ.
Thêm ID bản đồ vào ứng dụng của bạn
Để sử dụng mã bản đồ mà bạn đã tạo, hãy sửa đổi tệp activity_main.xml
và chuyển mã bản đồ vào thuộc tính map:mapId
của 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" />
Sau khi bạn hoàn tất bước này, hãy tiếp tục và chạy ứng dụng để xem bản đồ của bạn theo phong cách mà bạn đã chọn!
6. Thêm điểm đánh dấu
Trong nhiệm vụ này, bạn thêm điểm đánh dấu vào bản đồ thể hiện những địa điểm yêu thích mà bạn muốn đánh dấu trên bản đồ. Trước tiên, bạn truy xuất danh sách các địa điểm đã được cung cấp trong dự án dành cho người mới bắt đầu, sau đó thêm những địa điểm đó vào bản đồ. Trong ví dụ này, đây là những cửa hàng xe đạp.
Tham khảo Google Maps
Trước tiên, bạn cần lấy thông tin tham chiếu đến đối tượng GoogleMap
để có thể sử dụng các phương thức của đối tượng đó. Để làm việc đó, hãy thêm mã sau vào phương thức MainActivity.onCreate()
của bạn ngay sau lệnh gọi tới setContentView()
:
MainActivity.onCreate()
val mapFragment = supportFragmentManager.findFragmentById(
R.id.map_fragment
) as? SupportMapFragment
mapFragment?.getMapAsync { googleMap ->
addMarkers(googleMap)
}
Trước tiên, cách triển khai sẽ tìm thấy SupportMapFragment
mà bạn đã thêm ở bước trước bằng cách sử dụng phương thức findFragmentById()
trên đối tượng SupportFragmentManager
. Sau khi nhận được tệp đối chiếu, lệnh gọi getMapAsync()
sẽ được gọi, sau đó được chuyển vào hàm lambda. Hàm lambda này là nơi chuyển đối tượng GoogleMap
. Bên trong hàm lambda này, lệnh gọi phương thức addMarkers()
sẽ được gọi. Lệnh này sẽ sớm được xác định.
Lớp được cung cấp: Places Reader
Trong dự án dành cho người mới bắt đầu, bạn đã cung cấp lớp PlacesReader
. Lớp này đọc danh sách 49 địa điểm được lưu trữ trong tệp JSON có tên là places.json
và trả về các địa điểm này dưới dạng List<Place>
. Các địa điểm này đại diện cho danh sách các cửa hàng xe đạp quanh San Francisco, CA, Hoa Kỳ.
Nếu muốn biết cách triển khai lớp học này, bạn có thể truy cập vào lớp học trên GitHub hoặc mở lớp PlacesReader
trong Android Studio.
Places Reader
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()
}
}
Tải địa điểm
Để tải danh sách cửa hàng xe đạp, hãy thêm một tài sản trong MainActivity
có tên là places
và xác định tài sản như sau:
MainActivity.Places
private val places: List<Place> by lazy {
PlacesReader(this).read()
}
Mã này gọi phương thức read()
trên PlacesReader
, trả về List<Place>
. Place
có một thuộc tính có tên là name
, tên của địa điểm và latLng
– tọa độ của địa điểm.
Địa điểm
data class Place(
val name: String,
val latLng: LatLng,
val address: LatLng,
val rating: Float
)
Thêm điểm đánh dấu vào bản đồ
Bây giờ, danh sách các địa điểm đã được tải vào bộ nhớ, bước tiếp theo là trình bày các địa điểm này trên bản đồ.
- Tạo một phương thức trong
MainActivity
có tên làaddMarkers()
và xác định phương thức này như sau:
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)
)
}
}
Phương thức này lặp lại trong danh sách places
, sau đó gọi phương thức addMarker()
trên đối tượng GoogleMap
đã cung cấp. Điểm đánh dấu này được tạo bằng cách tạo đối tượng MarkerOptions
để bạn có thể tùy chỉnh chính điểm đánh dấu. Trong trường hợp này, tiêu đề và vị trí của điểm đánh dấu được cung cấp, đại diện cho tên cửa hàng xe đạp và tọa độ tương ứng.
- Hãy tiếp tục và chạy ứng dụng, cũng như sẽ đến San Francisco để xem những điểm đánh dấu bạn vừa thêm!
7. Tùy chỉnh điểm đánh dấu
Có một số tùy chọn tùy chỉnh cho các điểm đánh dấu bạn vừa thêm để giúp chúng nổi bật và truyền tải thông tin hữu ích cho người dùng. Trong nhiệm vụ này, bạn sẽ khám phá một số điểm bằng cách tùy chỉnh hình ảnh của mỗi điểm đánh dấu cũng như cửa sổ thông tin hiển thị khi điểm đánh dấu được nhấn vào.
Thêm cửa sổ thông tin
Theo mặc định, cửa sổ thông tin khi bạn nhấn vào một điểm đánh dấu sẽ hiển thị tiêu đề và đoạn trích của điểm đánh dấu (nếu được đặt). Bạn tùy chỉnh cài đặt này để có thể hiển thị thêm thông tin như địa chỉ và điểm xếp hạng của địa điểm.
Tạo timestamp_info_contents.xml
Trước tiên, hãy tạo một tệp bố cục mới có tên là marker_info_contents.xml
.
- Để thực hiện việc này, hãy nhấp chuột phải vào thư mục
app/src/main/res/layout
trong chế độ xem dự án trong Android Studio rồi chọn Mới > Tệp tài nguyên bố cục.
- Trong hộp thoại, hãy nhập
marker_info_contents
trong trường Tên tệp vàLinearLayout
vào trườngRoot element
, sau đó nhấp vào OK.
Tệp bố cục này sau đó được tăng cường để thể hiện nội dung trong cửa sổ thông tin.
- Sao chép nội dung trong đoạn mã sau, thao tác này thêm ba
TextViews
trong một nhóm chế độ xemLinearLayout
dọc và ghi đè mã mặc định trong tệp.
mark_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>
Tạo phương thức triển khai InfowindowAdapter
Sau khi tạo tệp bố cục cho cửa sổ thông tin tùy chỉnh, bước tiếp theo là triển khai giao diện GoogleMap.InfoInfoAdapter. Giao diện này chứa hai phương thức, getInfoWindow()
và getInfoContents()
. Cả hai phương thức đều trả về một đối tượng View
không bắt buộc, trong đó phương thức cũ được dùng để tùy chỉnh cửa sổ, trong khi phương thức thứ hai là tùy chỉnh nội dung của cửa sổ. Trong trường hợp của bạn, bạn triển khai cả hai và tùy chỉnh trả về getInfoContents()
trong khi trả về null trong getInfoWindow()
. Điều này cho biết nên sử dụng cửa sổ mặc định.
- Tạo một tệp mới trong Kotlin có tên là
MarkerInfoWindowAdapter
trong cùng một gói vớiMainActivity
bằng cách nhấp chuột phải vào thư mụcapp/src/main/java/com/google/codelabs/buildyourfirstmap
trong chế độ xem dự án trong Android Studio, sau đó chọn Mới > Tệp/Lớp Kotlin.
- Trong hộp thoại, hãy nhập
MarkerInfoWindowAdapter
và giữ cho Tệp được làm nổi bật.
- Sau khi bạn tạo tệp, hãy sao chép nội dung trong đoạn mã sau vào tệp mới của bạn.
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
}
}
Trong nội dung của phương thức getInfoContents()
, Điểm đánh dấu đã cung cấp trong phương thức sẽ được truyền tới loại Place
và nếu không thể truyền, phương thức này sẽ trả về null (bạn chưa đặt thuộc tính thẻ trên Marker
, nhưng bạn sẽ làm điều đó trong bước tiếp theo).
Tiếp theo, bố cục marker_info_contents.xml
được tăng cường, theo sau là đặt văn bản chứa TextViews
thành thẻ Place
.
Cập nhật MainActivity
Để dán tất cả thành phần bạn đã tạo từ trước đến nay, bạn cần thêm hai dòng trong lớp MainActivity
.
Trước tiên, để chuyển InfoWindowAdapter
tùy chỉnh, MarkerInfoWindowAdapter
, bên trong lệnh gọi phương thức getMapAsync
, hãy gọi phương thức setInfoWindowAdapter()
trên đối tượng GoogleMap
và tạo một thực thể mới của MarkerInfoWindowAdapter
.
- Hãy làm việc này bằng cách thêm mã sau đây vào lệnh gọi phương thức
addMarkers()
bên trong hàm lambdagetMapAsync()
.
MainActivity.onCreate()
// Set custom info window adapter
googleMap.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))
Cuối cùng, bạn sẽ cần đặt mỗi Địa điểm làm thuộc tính thẻ trên mọi Điểm đánh dấu được thêm vào bản đồ.
- Để thực hiện việc đó, hãy sửa đổi lệnh gọi
places.forEach{}
trong hàmaddMarkers()
bằng cách sau:
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
}
Thêm một hình ảnh điểm đánh dấu tùy chỉnh
Tùy chỉnh hình ảnh điểm đánh dấu là một trong những cách thú vị để truyền đạt loại địa điểm mà điểm đánh dấu đại diện trên bản đồ của bạn. Đối với bước này, bạn hiển thị xe đạp thay vì điểm đánh dấu màu đỏ mặc định để đại diện cho mỗi cửa hàng trên bản đồ. Dự án dành cho người mới bắt đầu bao gồm biểu tượng xe đạp ic_directions_bike_black_24dp.xml
trong app/src/res/drawable
mà bạn sử dụng.
Đặt bitmap tùy chỉnh trên điểm đánh dấu
Với biểu tượng xe đạp có thể vẽ vectơ theo ý bạn, bước tiếp theo là đặt biểu tượng có thể vẽ đó làm từng điểm đánh dấu#39; biểu tượng trên bản đồ. MarkerOptions
có một phương thức icon
. Phương thức này sử dụng BitmapDescriptor
mà bạn dùng để thực hiện việc này.
Trước tiên, bạn cần chuyển đổi vectơ vẽ mà bạn vừa thêm vào BitmapDescriptor
. Tệp có tên BitMapHelper
có trong dự án dành cho người mới bắt đầu chứa một hàm trợ giúp có tên là vectorToBitmap()
. Hàm này sẽ thực hiện việc này.
Trình trợ giúpBitmapHelper
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)
}
}
Phương thức này nhận Context
, một mã tài nguyên có thể vẽ cũng như một số nguyên màu và tạo một biểu thị BitmapDescriptor
về số đó.
Sử dụng phương thức trợ giúp, khai báo một thuộc tính mới có tên là bicycleIcon
và cung cấp định nghĩa sau cho thuộc tính: MainActivity.cyclingIcon
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)
}
Thuộc tính này sử dụng màu xác định trước colorPrimary
trong ứng dụng của bạn và dùng màu đó để phủ màu biểu tượng xe đạp và trả về dưới dạng BitmapDescriptor
.
- Bằng cách sử dụng thuộc tính này, hãy tiếp tục và gọi phương thức
icon
củaMarkerOptions
trong phương thứcaddMarkers()
để hoàn tất quá trình tùy chỉnh biểu tượng. Để làm điều này, thuộc tính điểm đánh dấu sẽ trông giống như sau:
MainActivity.addMarkers()
val marker = googleMap.addMarker(
MarkerOptions()
.title(place.name)
.position(place.latLng)
.icon(bicycleIcon)
)
- Chạy ứng dụng để xem các điểm đánh dấu được cập nhật!
8. Điểm đánh dấu cụm
Tùy thuộc vào khoảng cách mà bạn phóng to bản đồ, bạn có thể nhận thấy rằng các điểm đánh dấu mà bạn đã thêm trùng lặp. Các điểm đánh dấu trùng lặp rất khó tương tác và tạo ra nhiều tiếng ồn. Điều này ảnh hưởng đến khả năng hữu dụng của ứng dụng.
Để cải thiện trải nghiệm người dùng cho việc này, bất cứ khi nào bạn có một tập dữ liệu lớn được phân nhóm chặt chẽ, phương pháp hay nhất là triển khai việc phân nhóm điểm đánh dấu. Với tính năng nhóm, khi bạn phóng to và thu nhỏ bản đồ, các điểm đánh dấu ở gần sẽ được nhóm lại với nhau như sau:
Để triển khai chế độ này, bạn cần trợ giúp về SDK Maps dành cho Thư viện tiện ích Android.
SDK Maps dành cho Thư viện tiện ích Android
SDK Maps dành cho Thư viện tiện ích Android đã được tạo ra như một cách mở rộng chức năng của SDK Maps dành cho Android. Nền tảng này cung cấp các tính năng nâng cao, chẳng hạn như tính năng phân nhóm điểm đánh dấu, bản đồ nhiệt, hỗ trợ KML và GeoJson, mã hóa và giải mã nhiều đường cùng một số chức năng trợ giúp liên quan đến hình học hình cầu.
Cập nhật tệp build.gradle của bạn
Vì thư viện tiện ích được đóng gói riêng biệt với SDK Maps dành cho Android, nên bạn cần thêm một phần phụ thuộc bổ sung vào tệp build.gradle
.
- Hãy tiếp tục và cập nhật phần
dependencies
trong tệpapp/build.gradle
của bạn.
build.gradle
implementation 'com.google.maps.android:android-maps-utils:1.1.0'
- Khi thêm dòng này, bạn phải thực hiện đồng bộ hóa dự án để tìm nạp các phần phụ thuộc mới.
Triển khai tính năng phân nhóm
Để triển khai tính năng nhóm trên ứng dụng, hãy làm theo ba bước sau:
- Triển khai giao diện
ClusterItem
. - Lớp phụ lớp
DefaultClusterRenderer
. - Tạo
ClusterManager
và thêm các mục.
Triển khai giao diện ClusterItem
Tất cả các đối tượng đại diện cho điểm đánh dấu có thể phân nhóm trên bản đồ cần phải triển khai giao diện ClusterItem
. Trong trường hợp của bạn, điều đó có nghĩa là mô hình Place
cần tuân thủ ClusterItem
. Hãy tiếp tục và mở tệp Place.kt
và thực hiện các tùy chọn sửa đổi sau đối với tệp:
Địa điểm
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 xác định 3 phương thức sau:
getPosition()
, đại diện cho địa điểm củaLatLng
.getTitle()
, đại diện cho tên địa điểmgetSnippet()
, đại diện cho địa chỉ của địa điểm.
Lớp con của lớp DefaultClusterRenderinger
Lớp học triển khai việc phân nhóm, ClusterManager
, sử dụng nội bộ một lớp ClusterRenderer
để xử lý việc tạo các cụm khi bạn kéo và thu phóng xung quanh bản đồ. Theo mặc định, cấu hình này đi kèm với trình kết xuất mặc định DefaultClusterRenderer
sẽ triển khai ClusterRenderer
. Bạn chỉ cần cung cấp trường hợp này là đủ. Tuy nhiên, trong trường hợp của bạn, vì các điểm đánh dấu cần được tùy chỉnh, bạn cần phải mở rộng lớp này và thêm các tùy chỉnh trong đó.
Hãy tiếp tục và tạo tệp Kotlin PlaceRenderer.kt
trong gói com.google.codelabs.buildyourfirstmap.place
và xác định tệp như sau:
Trình kết xuất địa điểm
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
}
}
Lớp này ghi đè 2 hàm sau:
onBeforeClusterItemRendered()
, được gọi trước khi cụm được hiển thị trên bản đồ. Tại đây, bạn có thể cung cấp các tùy chỉnh thông quaMarkerOptions
. Trong trường hợp này, cột này đặt tiêu đề, vị trí và biểu tượng của điểm đánh dấu.onClusterItemRenderer()
, được gọi ngay sau khi điểm đánh dấu hiển thị trên bản đồ. Đây là nơi bạn có thể truy cập vào đối tượngMarker
đã tạo. Trong trường hợp này, đối tượng sẽ đặt thuộc tính thẻ của điểm đánh dấu\.
Tạo ClusterManager và thêm mục
Cuối cùng, để hoạt động phân nhóm hoạt động, bạn cần sửa đổi MainActivity
để tạo ClusterManager
và cung cấp các phần phụ thuộc cần thiết cho hoạt động này. ClusterManager
xử lý nội bộ việc thêm các điểm đánh dấu (các đối tượng ClusterItem
) nên thay vì thêm trực tiếp các điểm đánh dấu trên bản đồ, trách nhiệm này sẽ được ủy quyền cho ClusterManager
. Ngoài ra, ClusterManager
cũng gọi setInfoWindowAdapter()
trong nội bộ, do đó, việc đặt cửa sổ thông tin tùy chỉnh sẽ phải được thực hiện trên đối tượng MarkerManager.Collection
của ClusterManger
.
- Để bắt đầu, hãy sửa đổi nội dung của hàm lambda trong lệnh gọi
getMapAsync()
trongMainActivity.onCreate()
. Hãy tiếp tục và nhận xét cuộc gọi đếnaddMarkers()
vàsetInfoWindowAdapter()
, sau đó gọi một phương thức có tên làaddClusteredMarkers()
mà bạn xác định tiếp theo.
MainActivity.onCreate()
mapFragment?.getMapAsync { googleMap ->
//addMarkers(googleMap)
addClusteredMarkers(googleMap)
// Set custom info window adapter.
// googleMap.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))
}
- Tiếp theo, trong
MainActivity
, hãy xác địnhaddClusteredMarkers()
.
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()
}
}
Phương thức này tạo thực thể cho ClusterManager
, chuyển trình kết xuất tuỳ chỉnh PlacesRenderer
vào phương thức đó, thêm tất cả địa điểm và gọi phương thức cluster()
. Ngoài ra, vì ClusterManager
sử dụng phương thức setInfoWindowAdapter()
trên đối tượng bản đồ, nên việc đặt cửa sổ thông tin tùy chỉnh sẽ phải được thực hiện trên đối tượng ClusterManager.markerCollection
. Cuối cùng, vì bạn muốn nhóm lại để thay đổi khi người dùng kéo và thu phóng xung quanh bản đồ, nên OnCameraIdleListener
được cung cấp cho googleMap
, để khi máy ảnh chuyển sang chế độ không hoạt động, clusterManager.onCameraIdle()
sẽ được gọi.
- Hãy tiếp tục và chạy ứng dụng này để xem các cửa hàng theo cụm mới!
9. Vẽ trên bản đồ
Trong khi bạn đã khám phá một cách vẽ trên bản đồ (bằng cách thêm các điểm đánh dấu), SDK Maps dành cho Android sẽ hỗ trợ nhiều cách khác mà bạn có thể vẽ để hiển thị thông tin hữu ích trên bản đồ.
Ví dụ: nếu bạn muốn biểu thị các tuyến đường và khu vực trên bản đồ, bạn có thể sử dụng đa đường và đa giác để hiển thị chúng trên bản đồ. Nếu muốn sửa hình ảnh khỏi bề mặt của mặt đất, bạn có thể sử dụng lớp phủ mặt đất.
Trong nhiệm vụ này, bạn sẽ học cách vẽ hình dạng, cụ thể là một vòng tròn, xung quanh một điểm đánh dấu mỗi khi được nhấn.
Thêm trình xử lý lượt nhấp
Thông thường, cách thêm trình nghe lượt nhấp vào một điểm đánh dấu là chuyển trực tiếp trình nghe lượt nhấp vào đối tượng GoogleMap
thông qua setOnMarkerClickListener()
. Tuy nhiên, vì bạn đang sử dụng tính năng phân nhóm nên trình nghe lượt nhấp cần được cung cấp cho ClusterManager
.
- Trong phương thức
addClusteredMarkers()
trongMainActivity
, hãy tiếp tục và thêm dòng sau ngay sau lệnh gọi đếncluster()
.
MainActivity.addClusteredMarkers()
// Show polygon
clusterManager.setOnClusterItemClickListener { item ->
addCircle(googleMap, item)
return@setOnClusterItemClickListener false
}
Phương thức này thêm một trình nghe và gọi phương thức addCircle()
mà bạn xác định tiếp theo. Cuối cùng, false
được trả về từ phương thức này để cho biết rằng phương thức này không sử dụng sự kiện này.
- Tiếp theo, bạn cần xác định thuộc tính
circle
và phương thứcaddCircle()
trongMainActivity
.
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))
)
}
Bạn đã đặt thuộc tính circle
sao cho bất cứ khi nào nhấn vào điểm đánh dấu mới, thì vòng tròn trước đó sẽ bị xóa và một điểm mới sẽ được thêm vào. Lưu ý rằng API để thêm vòng tròn tương tự như việc thêm điểm đánh dấu.
- Hãy tiếp tục và chạy ứng dụng để xem các thay đổi này.
10. Điều khiển máy ảnh
Nhiệm vụ cuối cùng là xem một số bộ điều khiển máy ảnh để có thể tập trung vào chế độ xem xung quanh một khu vực nhất định.
Máy ảnh và chế độ xem
Nếu bạn nhận thấy khi chạy ứng dụng, máy ảnh sẽ hiển thị lục địa Châu Phi và bạn phải chăm chỉ và phóng to San Francisco để tìm các điểm đánh dấu mà bạn đã thêm. Mặc dù đây có thể là một cách thú vị để khám phá thế giới, nhưng sẽ không hữu ích nếu bạn muốn hiển thị các điểm đánh dấu ngay lập tức.
Để giúp bạn làm việc đó, bạn có thể đặt vị trí của máy ảnh theo phương thức lập trình để chế độ xem được căn giữa vị trí bạn muốn.
- Tiếp tục và thêm mã sau vào lệnh gọi
getMapAsync()
để điều chỉnh chế độ xem máy ảnh để được khởi chạy đến San Francisco khi ứng dụng được chạy.
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))
}
}
Trước tiên, setOnMapLoadedCallback()
sẽ được gọi để quá trình cập nhật máy ảnh chỉ được thực hiện sau khi tải bản đồ. Bước này là cần thiết vì các thuộc tính của bản đồ, chẳng hạn như thứ nguyên, cần phải được tính toán trước khi thực hiện lệnh gọi cập nhật máy ảnh.
Trong hàm lambda, một đối tượng LatLngBounds
mới được xây dựng để xác định khu vực hình chữ nhật trên bản đồ. Sản phẩm này được xây dựng dần dần bằng cách bao gồm tất cả giá trị của địa điểm LatLng
trong đó để đảm bảo tất cả các địa điểm nằm trong ranh giới. Sau khi đối tượng này được tạo, phương thức moveCamera()
trên GoogleMap
sẽ được gọi và CameraUpdate
sẽ được cung cấp cho đối tượng này thông qua CameraUpdateFactory.newLatLngBounds(bounds.build(), 20)
.
- Chạy ứng dụng và nhận thấy rằng máy ảnh hiện được khởi tạo ở San Francisco.
Nghe các thay đổi đối với máy ảnh
Ngoài việc sửa đổi vị trí của máy ảnh, bạn cũng có thể nghe thông tin cập nhật bằng máy ảnh khi người dùng di chuyển xung quanh bản đồ. Điều này có thể hữu ích nếu bạn muốn sửa đổi giao diện người dùng khi máy ảnh di chuyển xung quanh.
Để giải trí, bạn sửa đổi mã để làm cho các điểm đánh dấu bị mờ bất cứ khi nào máy ảnh được di chuyển.
- Trong phương thức
addClusteredMarkers()
, hãy tiếp tục và thêm các dòng sau vào cuối phương thức:
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 }
}
Thao tác này sẽ thêm OnCameraMoveStartedListener
để bất cứ khi nào máy ảnh bắt đầu di chuyển, tất cả các giá trị đánh dấu alpha
- Cuối cùng, để sửa đổi các điểm đánh dấu mờ trở về độ mờ khi máy ảnh dừng, hãy sửa đổi nội dung của
setOnCameraIdleListener
trong phương thứcaddClusteredMarkers()
thành phương thức sau:
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()
}
- Hãy tiếp tục và chạy ứng dụng để xem kết quả!
11. KTX trên Maps
Đối với các ứng dụng Kotlin sử dụng một hoặc nhiều SDK Android của Nền tảng Google Maps, tiện ích Kotlin hoặc thư viện KTX có sẵn để giúp bạn tận dụng các tính năng về ngôn ngữ của Kotlin, chẳng hạn như coroutine, thuộc tính/hàm mở rộng và nhiều tính năng khác. Mỗi SDK của Google Maps có một thư viện KTX tương ứng như được hiển thị dưới đây:
Trong nhiệm vụ này, bạn sẽ sử dụng thư viện KTX và Maps utils cho KTX trong ứng dụng của mình, đồng thời tái cấu trúc các thao tác trước đó\39; triển khai để bạn có thể sử dụng các tính năng ngôn ngữ dành riêng cho Kotlin trong ứng dụng.
- Bao gồm các phần phụ thuộc KTX trong tệp build.gradle cấp ứng dụng
Vì ứng dụng sử dụng cả SDK Maps cho Android và SDK Maps cho Thư viện tiện ích Android, nên bạn sẽ cần đưa các thư viện KTX tương ứng vào các thư viện này. Bạn cũng sẽ dùng một tính năng có trong thư viện KX Lifecycle KTX trong nhiệm vụ này, vì vậy, hãy đưa cả phần phụ thuộc đó vào tệp build.gradle
ở cấp ứng dụng.
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'
}
- Dùng các hàm mở rộng GoogleMap.addMarker() và GoogleMap.addCircle()
Thư viện KTX cung cấp một API thay thế kiểu DSL cho GoogleMap.addMarker(MarkerOptions)
và GoogleMap.addCircle(CircleOptions)
được sử dụng trong các bước trước. Để sử dụng các API nói trên, bạn cần xây dựng một lớp chứa các tùy chọn cho điểm đánh dấu hoặc vòng tròn, trong khi với các tùy chọn thay thế KTX, bạn có thể đặt các tùy chọn cho điểm đánh dấu hoặc vòng tròn trong hàm lambda mà bạn cung cấp.
Để sử dụng các API này, hãy cập nhật phương thức MainActivity.addMarkers(GoogleMap)
và 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))
}
}
Việc viết lại các phương thức trên theo cách ngắn gọn hơn nhiều nhờ cách đọc bằng hàm hàm với trình nhận Kotlin.
- Dùng các hàm tạm ngưng tiện ích SupportMapFragment.aWaitMap() và GoogleMap.aWaitMapLoad()
Thư viện Maps KTX cũng cung cấp việc tạm ngưng phần mở rộng về hàm để dùng trong coroutine. Cụ thể, có các thay thế hàm tạm ngưng cho SupportMapFragment.getMapAsync(OnMapReadyCallback)
và GoogleMap.setOnMapLoadedCallback(OnMapLoadedCallback)
. Khi sử dụng các API thay thế này, bạn không cần chuyển các lệnh gọi lại và thay vào đó cho phép bạn nhận phản hồi của các phương thức này theo cách nối tiếp và đồng bộ.
Vì các phương thức này đang tạm ngưng các hàm, nên việc sử dụng các phương thức này sẽ phải xảy ra trong một coroutine. Thư viện Lifecycle Runtime KTX cung cấp một phần mở rộng để cung cấp phạm vi coroutine nhận biết vòng đời để coroutine được chạy và dừng tại sự kiện trong vòng đời thích hợp.
Khi kết hợp các khái niệm này, hãy cập nhật phương thức MainActivity.onCreate(Bundle)
:
MainActivity.onCreate(Gói)
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)
}
}
Phạm vi coroutine của lifecycleScope.launchWhenCreated
sẽ thực thi khối khi ít nhất hoạt động ở trạng thái được tạo. Ngoài ra, hãy lưu ý rằng các lệnh gọi để truy xuất đối tượng GoogleMap
và để đợi bản đồ hoàn tất việc tải, đã được thay thế tương ứng bằng SupportMapFragment.awaitMap()
và GoogleMap.awaitMapLoad()
. Việc tái cấu trúc mã sử dụng các hàm tạm ngưng này cho phép bạn viết mã dựa trên lệnh gọi lại tương đương theo cách tuần tự.
- Hãy tiếp tục và xây dựng lại ứng dụng bằng các thay đổi đã được tái cấu trúc của bạn!
12. Xin chúc mừng
Xin chúc mừng! Bạn đã tìm hiểu rất nhiều nội dung và hy vọng rằng bạn đã hiểu rõ hơn về các tính năng cốt lõi được cung cấp trong SDK Maps dành cho Android.
Tìm hiểu thêm
- SDK địa điểm cho Android – khám phá tập hợp dữ liệu địa điểm phong phú để khám phá các doanh nghiệp xung quanh bạn.
- android-maps-ktx – một thư viện nguồn mở cho phép bạn tích hợp với SDK Maps dành cho Android và SDK Maps dành cho Thư viện tiện ích Android theo cách phù hợp với Kotlin.
- android-place-ktx – thư viện nguồn mở cho phép bạn tích hợp với SDK địa điểm dành cho Android theo cách thân thiện với Kotlin.
- android-samples – Mã mẫu trên GitHub minh họa tất cả các tính năng có trong lớp học lập trình này và nhiều tính năng khác.
- Các lớp học lập trình khác về Kotlin để xây dựng các ứng dụng Android thông qua Nền tảng Google Maps