開始使用 Google 導航 SDK
在本教學中,我們將實際操作 Google 導航 SDK(通常稱為 Nav SDK),通過使用官方導航 SDK Code Lab 提供的入門範例程式碼來建立一個簡單的 Android 導航應用程式。我們將說明如何設置 Nav SDK、確保您的應用程式擁有正確的權限和程式庫,並讓它提供到預設目的地的應用程式內駕駛路線。未來的部落格文章將示範如何允許使用者使用地點自動完成文字框(Place Autocomplete text box)設定目的地 地址、使用您的品牌自訂導航介面,以及調整主要 UI 元素的位置和可見性以獲得更好的控制。

第一篇:獨立使用 Google 導航 SDK
第二篇:開始使用 Google 導航 SDK(本文)
第三篇:在 Android 應用程式中新增地點自動完成文字框
第四篇:如何在 Android 的 Google 地圖上使用路線 API 畫路線
第五篇:使用自訂 UI 和品牌設計導航 SDK
第六篇:在導航視圖中使用自訂 Google 地圖標記
第七篇:使用導航 SDK 自訂你的車輛圖示
第八篇:在導航 SDK 中使用自訂多邊形、邊界和底圖樣式
第九篇:使用導航 SDK 進行多點導航
第十篇:將 GMPRO 或路線 API 路線令牌傳遞給導航 SDK
第十一篇:監聽事件並檢測到達目的地
第十二篇:Google 導航 SDK 與 Mapbox 與 HERE地圖比較
關於本 Google Navigation SDK 教學
本教學適合具有一般軟體開發經驗,但對 Android 行動開發或 Kotlin 程式語言尚未熟悉的開發者。要充分利用這份教學,有兩種方式: 如果你已經有一款行動應用程式,並希望將 Navigation SDK 作為現有應用內導航系統的即插即用替代方案,你可以直接 clone nav_sdk_tutorial 儲存庫,將你的 Google Maps API 金鑰 加入 local.defaults.properties 和 secrets.properties 檔案,然後在 Android Studio 中執行該應用程式。如果你想深入瞭解 Nav SDK 的運作細節,可以逐行跟著教學內容操作。若任何步驟遇到困難,你隨時可以參考 nav_sdk_tutorial 的 commit 歷史以取得指引。
Afi Labs 專注於打造客製化的交通與物流管理軟體。從路線最佳化、即時追蹤、到交付證明與司機應用程式,我們都能滿足您的需求!
Google Navigation SDK 的運作方式
為了在您的應用程式中啟用逐向導航(turn-by-turn navigation),Navigation SDK 需要一條預先定義好的路線。您可以透過指定起點(通常是使用者的當前位置)與目的地來建立這條路線,或者傳入由 Google Directions API、Routes API 或 GMPRO 產生的編碼折線(encoded polyline)。以下程式碼示範如何設定一個 Navigation SDK 服務,使其能接受路線並提供到指定目的地的逐向導航。
初始提交
要取得我們的起始程式碼,請使用以下指令 clone Codelab 儲存庫:git clone https://github.com/googlemaps-samples/codelab-navigation-101-android-kotlin.git.
這份程式碼由 Google 的解決方案工程師 Ed Boiling 撰寫,本教學基本上是對他的出色 Google Codelab: 使用 Google Maps Platform Navigation SDK 建立簡單的 Android 導航應用程式的重寫,我是依據自己對如何建立 Android 導航應用程式的基本理解(並借助 ChatGPT 的協助)完成的。我一直認為學習新語言或框架的最佳方式,是親自實作一段可運作的程式碼,這正是我在這裡所做的。通常,我會將他的可運作程式碼複製到大型語言模型中(ChatGPT-4o 尤其擅長這個任務,Anthropic 的最新模型也很好)來試著理解程式的運作方式。
本教學中的程式碼片段只顯示新增的程式碼,可能省略部分或全部周邊的現有程式碼。若要確認新程式碼的插入位置,請參考每個章節提供的 Git commit 差異截圖。
開始之前,請進入 Ed 的 Codelab 儲存庫 的 /Starter 目錄,將其內容複製並貼到一個名為 nav_sdk-afi_labs 的新資料夾中。

我們將使用這個程式碼基礎作為導航應用程式的起點。請下載 Android Studio,並打開 /nav_sdk-afi_labs 資料夾。點擊頂部導航列的 ▶️ 按鈕執行應用程式。您應該會在模擬器中看到一個空白的應用程式運行。這表示您的電腦已具備在本地執行 Android 應用程式所需的所有環境。

設定 SDK:新增依賴項
本教學的初始部分涉及 Gradle 的使用。Gradle 是一個開源的建置自動化工具,主要用於建置、測試及部署以 Java 和 Kotlin 撰寫的軟體。Gradle 使用基於 Kotlin 的領域專用語言(DSL),如果你習慣了 JavaScript 的語法,看到這個可能會覺得陌生,別擔心。
打開 app/build.gradle.kts 檔案,並在以下一行:
/*** app/build.gradle.kts ***/
api("com.google.android.libraries.navigation:navigation:5.2.3")加入到 dependencies 物件中。 這一行程式碼會新增對 com.google.android.libraries.navigation:navigation 函式庫(版本 5.2.3)的依賴,並確保在建置過程中自動下載並包含到你的專案中。

設定 SDK:配置 Navigation SDK API 金鑰
接下來,我們將使用在前一節中建立的 Navigation SDK 啟用的 API 金鑰,安全地與 Google Maps 伺服器進行驗證。我們會使用 Secrets Gradle Plugin,它的功能類似於 Node.js 開發中的 .env 檔案,為開發者提供安全存放 API 金鑰及其他憑證的方式,同時確保這些敏感資訊不會被提交到版本控制系統,降低意外洩露的風險。
首先,打開 app/build.gradle.kts,並將以下程式碼加入到 plugins 物件 中。
/*** app/build.gradle.kts ***/
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") version "2.0.1" apply false在同一個 app/build.gradle.kts 檔案中,新增一個 secrets 物件,並使用以下程式碼片段。這會為 Secrets Gradle Plugin 定義一個設定區塊。
/*** app/build.gradle.kts ***/
secrets {
// Optionally specify a different file name containing your secrets.
// The plugin defaults to "local.properties"
propertiesFileName = "secrets.properties"
// A properties file containing default secret values. This file can be
// checked in version control.
defaultPropertiesFileName = "local.defaults.properties"
}此插件會在建置過程中從指定的 secrets.properties 檔案讀取秘密資訊。通常,此檔案不會被提交到版本控制系統,以確保敏感資訊的安全。如果在 secrets.properties 中找不到某個秘密資訊,插件將會回退至 local.defaults.properties 檔案中的預設值。

接下來,建立一個新的檔案 secrets.properties,並加入你的 Google Maps API 金鑰(如果你打算將專案提交到版本控制系統,就不需要將金鑰加入 local.defaults.properties 檔案中)。
/*** secrets.properties ***/
MAPS_API_KEY=YOUR_API_KEY最後,儲存所有檔案,並與 Gradle 同步你的專案。

如果你在 Android Studio 中看不到 secrets.properties 檔案,表示左側側邊欄目前沒有使用 Project 視圖。

Nav SDK 在初始化時會自動從 AndroidManifest.xml 中取得 API 金鑰,因此你不需要在程式碼中另外加入它。
設定權限:宣告應用程式將使用的權限
在 app/src/main/AndroidManifest.xml 中,加入以下程式碼:
/*** app/src/main/AndroidManifest.xml ***/
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />在 Android 行動開發中,AndroidManifest.xml 檔案是我們應用程式的藍圖。它向 Android 系統提供關於應用程式的基本資訊,包括結構、行為及需求。在此,我們指定應用程式所需的 與位置相關的存取類型,例如允許應用程式在前景服務運行時存取位置服務。

授予我們的應用程式存取手機位置服務的權限,將允許 Nav SDK 追蹤我們的即時位置,並在偏離當前路線時 建議新的路線。
設定權限:請求存取權限
在宣告應用程式所需的定位權限後,我們必須檢查這些權限是否已被授予,並在未授權時提供簡單的介面讓使用者批准存取。
首先,將以下 import 語句 加入到 app/src/main/java/com/example/navsdkcodelab/MainActivity.kt 的最前面。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Looper
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat這些函式庫用於 處理權限、版本檢查及背景任務。

請將以下程式碼複製到 MainActivity.kt 中。這段程式碼定義了一個新的方法 requestAccessPermissions(),用來檢查使用者是否已授予定位存取權限。此方法會處理所有可能的情況,並在尚未授權時請求使用者授權。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
private fun requestAccessPermissions() {
val permissions =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.POST_NOTIFICATIONS
)
} else {
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
}
if (permissions.any { !checkPermissionGranted(it) }) {
if (permissions.any { shouldShowRequestPermissionRationale(it) }) {
// Display a dialogue explaining the required permissions.
}
val permissionsLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
permissionResults ->
if (
permissionResults.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false)
) {
onLocationPermissionGranted()
} else {
finish()
}
}
permissionsLauncher.launch(permissions)
} else {
android.os
.Handler(Looper.getMainLooper())
.postDelayed({ onLocationPermissionGranted() }, SPLASH_SCREEN_DELAY_MILLIS)
}
}
private fun checkPermissionGranted(permissionToCheck: String): Boolean =
ContextCompat.checkSelfPermission(this, permissionToCheck) == PackageManager.PERMISSION_GRANTED
private fun onLocationPermissionGranted() {}
當然,別忘了在 onCreate() 方法中 呼叫 requestAccessPermissions()。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
override fun onCreate(savedInstanceState: Bundle?) {
requestAccessPermissions()
}此方法會在 每次應用程式載入時執行。

最後,定義一個常數 SPLASH_SCREEN_DELAY_MILLIS,用來設定在執行 onLocationPermissionGranted() 方法(待撰寫)之前的延遲時間。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
companion object {
const val SPLASH_SCREEN_DELAY_MILLIS = 1000L
}
這段程式碼在 MainActivity 類別中定義了一個 companion object(Kotlin 的伴生物件)。它的作用類似於 Java 中的 static 物件,用來定義常數。

設定使用者介面:新增導航視圖
現在,我們將新增一個 布局 來顯示應用程式內的導航視圖。在 app/src/main/res/layout/activity_main.xml 中,點擊右上角較不顯眼的 [code] 按鈕,切換到程式碼視圖。

將 activity_main.xml 中現有的程式碼替換為以下內容:
/*** app/src/main/res/ayout/activity_main.xml ***/
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
<com.google.android.libraries.navigation.NavigationView android:id="@+id/navigation_view" android:layout_width="wrap_content" android:layout_height="wrap_content" />
</RelativeLayout>這段程式碼新增了一個 父容器,並包含一個 子視圖,用來放置 Nav SDK 的 NavigationView。

設定使用者介面:配置導航視圖的生命週期
為了讓 Nav SDK 正常運作,我們需要先進行一些基本設定,將其與 Android 元件的生命週期綁定。首先,將以下程式碼加入到 app/src/main/java/com/example/navsdkcodelab/MainActivity.kt 的最前面。
/*** app/src/main/res/ayout/activity_main.xml ***/
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
<com.google.android.libraries.navigation.NavigationView android:id="@+id/navigation_view" android:layout_width="wrap_content" android:layout_height="wrap_content" />
</RelativeLayout>MainActivity.kt 是我們應用程式的 入口點,負責管理使用者介面,讓你可以定義在 UI 元件的事件生命週期中各個階段應該執行的操作(例如 onCreate、onStart、onResume、onPause 和 onDestroy)。這兩行程式碼則是 匯入負責裝置配置(直向或橫向)以及導航相關功能的類別。

其次,在 MainActivity 類別中 宣告一個變數 navView,用來參考 NavigationView —— 這是布局中的 Nav SDK UI 元件,稍後會在 onCreate() 方法中進行初始化。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
private lateinit var navView: NavigationView
第三,在 onCreate() 方法中,使用 setContentView 將定義應用程式使用者介面的 布局資源檔案(通常位於 res/layout)設定為 activity_main.xml。這會將其轉換成一個 使用者介面視圖層級結構(例如按鈕、文字視圖等),讓 MainActivity 可以使用。我們同時將 activity_main.xml 中定義的 NavigationView 綁定到 navView 變數,並進行初始化。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
setContentView(R.layout.activity_main)
navView = findViewById(R.id.navigation_view)
navView.onCreate(savedInstanceState)
最後,新增一些 樣板程式碼,用來管理 navView 的事件生命週期。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
override fun onStart() {
super.onStart()
navView.onStart()
}
override fun onResume() {
super.onResume()
navView.onResume()
}
override fun onPause() {
navView.onPause()
super.onPause()
}
override fun onConfigurationChanged(configuration: Configuration) {
super.onConfigurationChanged(configuration)
navView.onConfigurationChanged(configuration)
}
override fun onStop() {
navView.onStop()
super.onStop()
}
override fun onDestroy() {
navView.onDestroy()
super.onDestroy()
}這可以確保 navView 能夠即時接收到主要的生命週期變化。每個被覆寫的方法對應到 NavigationView 的特定階段(如 onStart()、onStop()、onResume() 等),navView 物件會接收到這些生命週期事件,從而在這些階段執行自身操作或自訂程式碼。

如果你現在執行應用程式,會看到 Google 地圖 以 Google 世界總部(1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA)為中心。預設情況下,地圖會依據電腦時間顯示 「日間模式」或「夜間模式」。如果想要強制地圖始終使用 日間模式,請加入以下程式碼:
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
navView.setForceNightMode(ForceNightMode.FORCE_DAY)將其加入到 onCreate 方法中。其他選項包括:
- ForceNightMode.AUTO:指定 Navigation SDK 根據使用者所在地點與當地時間自動決定使用日間模式或夜間模式。
- ForceNightMode.FORCE_NIGHT:強制使用夜間模式。

設定使用者介面:配置導航引導期間螢幕保持常亮
任何應用程式內導航系統的一個關鍵功能是,當應用程式正在使用時,螢幕會保持常亮。要在 Android 中實現此功能,請在 MainActivity.kt 中從 android.view 套件匯入 WindowManager 類別。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
import android.view.WindowManager在 onCreate() 方法的下方,加入以下程式碼:
override fun onCreate(savedInstanceState: Bundle?) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
當將 FLAG_KEEP_SCREEN_ON 標誌加入應用程式的視窗屬性時,可以防止螢幕在閒置幾分鐘後自動關閉。

初始化 Navigation SDK:在權限可用時驗證 Navigation SDK 可用性
現在我們已完成基本的導航活動設定,接下來準備正式呼叫 Navigation SDK。但在此之前,我們需要匯入正確的函式庫。在 MainActivity.kt 的最上方加入:
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
import com.google.android.libraries.navigation.NavigationApi
import com.google.android.libraries.navigation.Navigator
import android.widget.Toast
import android.annotation.SuppressLint這段程式碼引入了 與導航相關的功能(NavigationApi 與 Navigator),用於管理 Nav SDK;同時引入 使用者通知功能(Toast),在應用程式互動時向使用者提供簡短訊息;以及 SuppressLint,用來在建置時避免令人困擾的 Lint 警告,讓開發更安心。

接下來,建立一個 對 Navigator 物件的參考(mNavigator)。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
private var mNavigator: Navigator? = null上述程式碼使用了 延遲初始化,確保 Navigator 實例僅在導航流程開始時才被建立。

接下來,你需要建立一個名為 initializeNavigationApi() 的新方法。
這個方法需要透過 mNavigator = navigator 這行程式碼來取得 Navigator 物件,
並同時實作 NavigatorListener 以處理相關的回呼。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
private fun onLocationPermissionGranted() {
initializeNavigationApi()
}
@SuppressLint("MissingPermission")
private fun initializeNavigationApi() {
val listener =
object : NavigationApi.NavigatorListener {
override fun onNavigatorReady(navigator: Navigator) {
mNavigator = navigator
}
override fun onError(@NavigationApi.ErrorCode errorCode: Int) {
when (errorCode) {
NavigationApi.ErrorCode.NOT_AUTHORIZED -> {
// Note: If this message is displayed, you may need to check that
// your API_KEY is specified correctly in AndroidManifest.xml
// and is been enabled to access the Navigation API
showToast(
"Error loading Navigation API: Your API key is " +
"invalid or not authorized to use Navigation."
)
}
NavigationApi.ErrorCode.TERMS_NOT_ACCEPTED -> {
showToast(
"Error loading Navigation API: User did not " +
"accept the Navigation Terms of Use."
)
}
else -> showToast("Error loading Navigation API: $errorCode")
}
}
}
NavigationApi.getNavigator(this, listener)
}
initializeNavigationApi() 會在使用者授予定位權限後被呼叫。

最後,我們新增一個 函式,用來透過 Android 的 Toast 功能顯示錯誤訊息。
private fun showToast(errorMessage: String) {
Toast.makeText(this@MainActivity, errorMessage, Toast.LENGTH_LONG).show()
}
這會在發生錯誤時,彈出一個 提示視窗 顯示錯誤訊息,提供使用者參考。

現在是 測試應用程式是否運作 的好時機。在 Android Studio 中,首先執行建置(Build > Make Project)。如果程式碼編譯沒有錯誤,就按下 ▶️ 執行按鈕,在模擬器中啟動應用程式。

如果一切順利,您會看到一個彈出視窗,請求位置權限(如上所示)。
實作邏輯:註冊導航事件變更
在應用程式內的導航過程中,Navigation SDK 會觸發事件,以通知應用程式路線上的重要狀態變更,例如使用者偏離路線或抵達目的地。在本節中,我們將實作監聽器來處理這些事件。
首先,在 MainActivity.kt 中宣告兩個變數,用來參考事件監聽器物件。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
private var arrivalListener: Navigator.ArrivalListener? = null
private var routeChangedListener: Navigator.RouteChangedListener? = null在這裡,我們使用廣泛應用的 觀察者 設計模式(Observer Design Pattern),將觸發抵達與路線變更事件的 Navigator 類別,與對這些事件作出反應的程式碼(監聽器實作)解耦。

接著,新增一個 registerNavigationListeners() 方法,用來在 Navigator 初始化時設定監聽器。此方法會呼叫 Navigator.clearDestinations(),以在抵達事件觸發時重置 NavigationView:
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
private fun registerNavigationListeners() {
arrivalListener =
Navigator.ArrivalListener { // Show an onscreen message
showToast("User has arrived at the destination!")
mNavigator?.clearDestinations()
}
mNavigator?.addArrivalListener(arrivalListener)
routeChangedListener =
Navigator.RouteChangedListener { // Show an onscreen message when the route changes
showToast("onRouteChanged: the driver's route changed")
}
mNavigator?.addRouteChangedListener(routeChangedListener)
}最後,在先前建立的 NavigationListener 物件中 呼叫 registerNavigationListeners()。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
override fun onNavigatorReady(navigator: Navigator) {
registerNavigationListeners()
}
有了這個設定後,我們現在可以開始 新增程式碼來處理這些事件。

實作邏輯:在應用程式從最近使用的應用列表移除時關閉導航服務
在完成大部分 Nav SDK 實作工作後,接下來的章節將簡短說明,重點在於確保應用程式內的導航模組能順利運作並保持穩定。
在 MainActivity.kt 的 onNavigatorReady() 方法中,加入以下程式碼:
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
override fun onNavigatorReady(navigator: Navigator) { navigator.setTaskRemovedBehavior(Navigator.TaskRemovedBehavior.QUIT_SERVICE)
}
這行程式碼告訴 Android,當應用程式的任務被移除時(例如使用者從最近使用的應用程式畫面向左或向右滑動關閉應用程式),Navigator 應停止正在執行的服務並自行終止。

實作邏輯:設定地圖鏡頭追蹤我的位置
Navigation SDK 的一個重要功能,也是其受歡迎的原因之一,是能夠為使用者提供 傾斜的 3D 路線視圖。要啟用此功能,請建立一個自訂方法 setupCameraFollowMyLocation(),並在 MainActivity.kt 的 onNavigatorReady() 方法中呼叫它。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
import com.google.android.gms.maps.GoogleMap
override fun onNavigatorReady(navigator: Navigator) {
setupCameraFollowMyLocation()
}
@SuppressLint("MissingPermission")
private fun setupCameraFollowMyLocation() {
navView.getMapAsync { googleMap ->
googleMap.followMyLocation(GoogleMap.CameraPerspective.TILTED)
}
}setupCameraFollowMyLocation() 方法內的程式碼,如其名稱所示,會 調整地圖鏡頭的位置,使其隨使用者移動時保持以使用者當前位置為中心。傳入 GoogleMap.CameraPerspective.TILTED 參數則會將地圖以 傾斜的 3D 視角 顯示,提供更直觀的導航路線視覺效果。

有三種常用的地圖鏡頭角度:
- CameraPerspective.TILTED:提供熟悉且受歡迎的 傾斜 3D 視角。
- CameraPerspective.TOP_DOWN_NORTH_UP:將鏡頭固定在車輛位置,地圖永遠 朝北顯示。
- CameraPerspective.TOP_DOWN_HEADING_UP:將鏡頭固定在車輛位置,地圖朝向 行駛方向 顯示。

實作邏輯:設定使用者位置,並啟用模擬模式
快完成了!為了在本地機器上有效測試 Navigation SDK,模擬使用者駕駛行為 是非常重要的。為此,我們將使用 SDK 提供的 Simulator 介面,它允許你模擬使用者位置並沿預先定義的路線模擬行駛。
在 MainActivity.kt 中,匯入 Google Maps Android API 的 LatLng 類別。這個類別表示具有緯度和經度的地理點(以度數表示),在使用 Google Maps 指定位置時非常常用。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
import com.google.android.gms.maps.model.LatLng接著,在 MainActivity 類別中,定義一個唯讀屬性 isSimulationMode,目前(暫時)永遠回傳 true。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
class MainActivity : AppCompatActivity() {
private val isSimulationMode get() = true
}
這樣一來,isSimulationMode 在 MainActivity 類別中被訪問時會評估為 true,從而在 Nav SDK 中觸發駕駛模擬。為什麼不直接將 isSimulationMode 設為 true 呢?其實,通過使用 get() 自訂 getter,我們可以在不改變屬性基本介面的前提下對 isSimulationMode 做未來的修改。例如,我們可能希望將 isSimulationMode 的值設定為遵循某個配置設置或環境變數,例如 private val isSimulationMode: Boolean get() = AppConfig.isSimulationEnabled 將遵循 AppConfig 檔案中 isSimulationEnabled 的值。
之後,向 companion object 添加一個常數 val startLocation = LatLng(49.2846992,-123.1138264),這樣我們就可以將 Nav SDK 的起點座標傳入,該座標取自加拿大不列顛哥倫比亞省溫哥華市中心的一個地址。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
companion object {
val startLocation = LatLng(49.2847001, -123.1145098)
}最後,在 onNavigatorReady() 監聽器中,新增程式碼以模擬使用者在特定位置的定位,而無需實際移動或使用啟用 GPS 的行動裝置。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
override fun onNavigatorReady(navigator: Navigator) {
if (isSimulationMode) {
mNavigator?.simulator?.setUserLocation(startLocation)
}
}回想一下,在本教程開始時,我提到過 Navigation SDK 需要起點和終點。我們在本節中已經加入了起點(起始位置)。接下來,我們將在下一節加入終點(目的地)。

實作邏輯:開始導航至目的地
現在您已準備好設定目的地並開始導航引導。在 MainActivity.kt 檔案中,進行以下修改。
在 MainActivity.kt 的最上方,從 Google Maps Navigation SDK 匯入 Waypoint 類別。我們將使用 Waypoint 類別將 Google Maps 的 place_id 轉換為可以傳遞給 Nav SDK 的有效目的地。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
import com.google.android.libraries.navigation.Waypoint在 companion 物件中,新增 const val WEST_POINT_GREY = "ChIJS09_Ne5yhlQRK1JX6bCnfn0" 以定義我們的終點位置。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
companion object {
const val WEST_POINT_GREY = "ChIJS09_Ne5yhlQRK1JX6bCnfn0"
}我們本可以像設定起點位置一樣,直接使用緯度和經度座標,但我想示範你也可以使用 Google Maps 的 place_id。

接著,我們撰寫一個新的方法 navigateToPlace(),以啟動前往指定目的地的導航。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
private fun navigateToPlace(placeId: String) {
val waypoint: Waypoint? =
try {
Waypoint.builder().setPlaceIdString(placeId).build()
} catch (e: Waypoint.UnsupportedPlaceIdException) {
showToast("Place ID was unsupported.")
return
}
val pendingRoute = mNavigator?.setDestination(waypoint)
pendingRoute?.setOnResultListener { code ->
when (code) {
Navigator.RouteStatus.OK -> {}
Navigator.RouteStatus.ROUTE_CANCELED -> showToast("Route guidance canceled.")
Navigator.RouteStatus.NO_ROUTE_FOUND,
Navigator.RouteStatus.NETWORK_ERROR -> showToast("Error starting guidance: $code")
else -> showToast("Error starting guidance: $code")
}
}
}
navigateToPlace() 接受一個 place_id 作為輸入,並開始導航至目的地(起點位置應在呼叫 navigateToPlace() 之前已設定)。

以下是這個方法如何運作的詳細說明:
- 目的地的航點表示形式已建立。如果
placeId無效或不受支持,錯誤會被妥理,並且錯誤訊息會以提示訊息的方式顯示。 - 使用所建立的
Waypoint呼叫Navigator物件(mNavigator)的setDestination方法。它會回傳一個pendingRoute,該物件代表路線計算過程的狀態。 pendingRoute其實是一個ListenableResultFuture物件(參考文件),其中包含一個RouteStatus物件。RouteStatus用來指示是否找到通往目的地的路線,並允許您在未找到路線時處理各種錯誤狀態。- 最後,在設定起點位置後,於 onNavigatorReady() 方法中呼叫 navigateToPlace()。
本節中的程式碼是一個出色的範例(不是我寫的),展示了如何在 Android 應用程式中優雅地處理錯誤,同時向使用者清楚地反饋發生了什麼問題。

實作邏輯:開始導航時隱藏頂部操作列
為了確保應用程式內的導航畫面不受干擾,我們將隱藏頂部操作列。在目前為空的 Navigator.RouteStatus.OK 情況中,加入以下程式碼:
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
when (code) {
Navigator.RouteStatus.OK -> {
supportActionBar?.hide()
}
}當您想要打造全螢幕體驗時,這會非常有用。

實作邏輯:啟動時導航 - 啟用語音與指引
現在我們需要將應用內導航「打開」。在 Navigator.RouteStatus.OK 的情況下,新增這一行 mNavigator?.startGuidance() 以開始逐步導航過程。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
when (code) {
Navigator.RouteStatus.OK -> {
mNavigator?.setAudioGuidance(Navigator.AudioGuidance.VOICE_ALERTS_AND_GUIDANCE)
mNavigator?.startGuidance()
}
}
啟用 Nav SDK 後,它即可使用裝置的 GPS 位置,引導使用者沿著計算出的路線前進。

實作邏輯:啟動時導覽 - 如果是模擬模式則模擬移動
即使 Navigation SDK 已正確設置和初始化,在本地機器上進行完整測試仍不可行,因為沒有實際移動。為了解決這個問題,我們將使用 SimulationOptions 套件來配置和管理導航模擬,使我們能在沒有實際移動或 GPS 資料的情況下測試導航行為。
要開始,請在 MainActivity.kt 中導入 SimulationOptions 函式庫。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
import com.google.android.libraries.navigation.SimulationOptions
然後,在 Navigator.RouteStatus.OK 情況下,加入 simulateLocationsAlongExistingRoute() 方法來模擬使用者沿路線行駛。
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
if (isSimulationMode) {
mNavigator
?.simulator
?.simulateLocationsAlongExistingRoute(SimulationOptions().speedMultiplier(5f))
}在這裡,我們加入 speedMultiplier(5f),將模擬速度調整為比現實速度快 5 倍。例如,一條 15 分鐘的路線,大約只需 3 分鐘即可完成模擬。這使得重新執行程式碼以進行除錯和測試更加方便。

這大概是確認程式碼能夠正確編譯且 Nav SDK 運作正常的好時機。點擊頂部導航列的 ▶️ 按鈕執行應用程式,您應該會看到鏡頭以藍色箭頭標示的移動車輛為中心。

實作邏輯:應用程式關閉時清理導航資源
最後,我們進行一些清理操作,以防止記憶體洩漏並釋放資源。在 MainActivity.kt 的 onDestroy() 方法中加入:
/*** app/src/main/java/com/example/navsdkcodelab/MainActivity.kt ***/
override fun onDestroy() {
mNavigator?.also { navigator ->
if (arrivalListener != null) {
navigator.removeArrivalListener(arrivalListener)
}
if (routeChangedListener != null) {
navigator.removeRouteChangedListener(routeChangedListener)
}
navigator.simulator?.unsetUserLocation()
navigator.cleanup()
}
mNavigator = null
}這可確保應用程式釋放不再需要的物件參考,防止記憶體洩漏。此外,如果在導航中使用了模擬器(例如在測試期間),它會重設使用者位置,以確保已關閉的 Nav SDK 不會影響後續操作。

接下來是什麼

恭喜你使用 Google Navigation SDK 建立了第一個應用程式——這是一個非常重要的成就!你現在可以在自己的應用程式內提供導航服務,而不必將使用者導向第三方服務,藉此提升使用者體驗,並加入符合你工作流程的自訂品牌與功能。在接下來的教學中,我將示範如何做到這一點!
👋 一如既往,如果你有任何問題或建議,歡迎隨時與我聯絡,或在 LinkedIn 上打聲招呼。