您可以使用 Navigation SDK for Android,決定要在地圖上顯示哪些內建 UI 控制項和元素,藉此修改地圖的使用者體驗。您也可以調整導覽 UI 的視覺外觀。如要瞭解可接受的導覽 UI 修改方式,請參閱政策頁面。
本文說明如何透過兩種方式修改地圖的使用者介面:
地圖 UI 控制項
建議您使用地圖 UI 控制項,在導覽檢視畫面上放置自訂 UI 元素,確保正確的定位。內建版面配置變更時,Android 版 Navigation SDK 會自動重新調整自訂控制項的位置。您可以為每個位置一次設定一個自訂控制項檢視畫面。如果設計需要多個 UI 元素,您可以將這些元素放入 ViewGroup
,並將其傳遞至 setCustomControl
方法。
setCustomControl
方法會提供 CustomControlPosition
列舉中定義的位置:
SECONDARY_HEADER
(僅會在直向模式下顯示)BOTTOM_START_BELOW
BOTTOM_END_BELOW
FOOTER


新增自訂控制項
- 使用自訂 UI 元素或 ViewGroup 建立 Android View。
- 以 XML 格式顯示或以例項化方式取得自訂檢視畫面的例項。
使用
NavigationView.setCustomControl
或SupportNavigationFragment.setCustomControl
,並搭配CustomControlPosition
enum 中所選的自訂控制項位置。以下範例會建立片段,並在次要標頭位置新增自訂控制項。
mNavFragment.setCustomControl(getLayoutInflater(). inflate(R.layout.your_custom_control, null), CustomControlPosition.SECONDARY_HEADER); ```
移除自訂控制項
如要移除自訂控制項,請使用 null
檢視畫面參數和所選自訂控制項位置,呼叫 setCustomControl
方法。
例如,以下程式碼片段會移除任何自訂次要標題,並傳回預設內容:
mNavFragment.setCustomControl(null, CustomControlPosition.SECONDARY_HEADER);
自訂控制項位置
次要標頭

如要使用這個自訂控制項位置,請將位置 CustomControlPosition.SECONDARY_HEADER
傳遞至 setCustomControl
。
根據預設,導覽模式中的畫面版面配置會為次要標題提供一個位於主要標題下方的位址。必要時 (例如在車道導航時),系統會顯示這個次要標頭。應用程式可以使用這個次要標題位置,為自訂內容安排版面配置。使用這項功能時,控制項會覆蓋任何預設的次要標頭內容。如果導覽檢視畫面含有背景,該背景會保持原位,並由次要標頭覆蓋。當應用程式移除自訂控制項時,任何預設的次要標頭都會顯示在該位置。
自訂次要標題位置會將頂端邊緣與主要標題的底部邊緣對齊。這個位置僅支援 portrait mode
。在 landscape mode
中,次要標頭無法使用,且版面配置不會變更。
從底部開始


如要使用這個自訂控制項位置,請將位置 CustomControlPosition.BOTTOM_START_BELOW
傳遞至 setCustomControl
。
這個自訂控制項位置位於地圖的底部起始角落。在 portrait mode
和 landscape mode
中,這會位於預估到達時間資訊卡和/或自訂頁尾上方 (如果兩者皆不存在,則會位於地圖底部),而 Nav SDK 元素 (包括重新置中按鈕和 Google 標誌) 會向上移動,以便考量自訂控制項檢視畫面的高度。這個控制項會置於可見地圖邊界內,因此新增至地圖底部或起始邊緣的任何邊距,也會變更這個控制項的位置。
底端


如要使用這個自訂控制項位置,請將位置 CustomControlPosition.BOTTOM_END_BELOW
傳遞至 setCustomControl
。
這個自訂控制項位置位於地圖的底端角落。在 portrait mode
中,資訊卡會位於預計到達時間資訊卡和/或自訂頁尾上方 (如果兩者皆不存在,則會沿著地圖底部),但在 landscape mode
中,資訊卡會與地圖底部對齊。沿著端側 (LTR 的右側) 顯示的任何 Nav SDK 元素都會向上移動,以便考量自訂控制項檢視畫面的高度。這個控制項會置於可見地圖邊界內,因此新增至地圖底部或端邊的邊框間距,也會變更這個控制項的位置。
頁尾


如要使用這個自訂控制項位置,請將位置 CustomControlPosition.FOOTER
傳遞至 setCustomControl
。
這個自訂控制項位置專為自訂頁尾檢視畫面而設計。如果您看到 Nav SDK ETA 資訊卡,這個控制項會顯示在資訊卡上方。如果沒有,控制項會與地圖底部對齊。與 BOTTOM_START_BELOW
和 BOTTOM_END_BELOW
自訂控制項不同,這個控制項會置於可見地圖邊界之外,也就是說,新增至地圖的任何邊框都不會變更這個控制項的位置。
在 portrait mode
中,自訂頁尾會是全寬。CustomControlPosition.BOTTOM_START_BELOW
和 CustomControlPosition.BOTTOM_END_BELOW
位置的自訂控制項,以及重新置準心按鈕和 Google 標誌等 Nav SDK UI 元素,會位於自訂控制項頁尾上方。箭頭的預設位置會考量自訂頁尾高度。
在 landscape mode
中,自訂頁尾的寬度為一半,並與起始邊 (LTR 中的左側) 對齊,就像 Nav SDK ETA 資訊卡一樣。CustomControlPosition.BOTTOM_START_BELOW
位置中的自訂控制項,以及重新置中按鈕和 Google 標誌等 Nav SDK UI 元素,會位於自訂控制項頁尾的上方。CustomControlPosition.BOTTOM_END_BELOW
位置的自訂控制項,以及沿著端側 (LTR 為右側) 的任何 Nav SDK UI 元素,都會與地圖底部對齊。自訂頁尾不會延伸至地圖的結尾,因此在自訂頁尾存在時,箭頭的預設位置不會改變。
CustomControlPosition.BOTTOM_START_BELOW
和 CustomControlPosition.BOTTOM_END_BELOW
位置的自訂控制項,以及重新置中心按鈕和 Google 標誌等 Nav SDK UI 元素,會位於自訂控制項頁尾的上方。
地圖 UI 配件
Navigation SDK for Android 提供 UI 配件,可在導航期間顯示類似於 Google 地圖 Android 應用程式中的配件。您可以按照本節所述調整這些控制項的顯示設定或外觀。您在這裡所做的變更會在下一個導覽工作階段中反映。
如要瞭解可接受的導覽 UI 修改方式,請參閱政策頁面。
查看程式碼
顯示/隱藏導覽活動的 Java 程式碼。
package com.example.navsdkcustomization; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.util.Log; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.GoogleMap.CameraPerspective; import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.model.BitmapDescriptorFactory; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import com.google.android.libraries.navigation.ListenableResultFuture; import com.google.android.libraries.navigation.NavigationApi; import com.google.android.libraries.navigation.Navigator; import com.google.android.libraries.navigation.SimulationOptions; import com.google.android.libraries.navigation.StylingOptions; import com.google.android.libraries.navigation.SupportNavigationFragment; import com.google.android.libraries.navigation.Waypoint; /** An activity that displays a map and a customized navigation UI. */ public class NavigationActivityCustomization extends AppCompatActivity { private static final String TAG = NavigationActivityCustomization.class.getSimpleName(); private Navigator mNavigator; private SupportNavigationFragment mNavFragment; private GoogleMap mMap; // Define the Sydney Opera House by specifying its place ID. private static final String SYDNEY_OPERA_HOUSE = "ChIJ3S-JXmauEmsRUcIaWtf4MzE"; // Set fields for requesting location permission. private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1; private boolean mLocationPermissionGranted; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Initialize the Navigation SDK. initializeNavigationSdk(); } /** * Starts the Navigation SDK and sets the camera to follow the device's location. Calls the * navigateToPlace() method when the navigator is ready. */ private void initializeNavigationSdk() { /* * Request location permission, so that we can get the location of the * device. The result of the permission request is handled by a callback, * onRequestPermissionsResult. */ if (ContextCompat.checkSelfPermission( this.getApplicationContext(), android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { mLocationPermissionGranted = true; } else { ActivityCompat.requestPermissions( this, new String[] {android.Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION); } if (!mLocationPermissionGranted) { displayMessage( "Error loading Navigation SDK: " + "The user has not granted location permission."); return; } // Get a navigator. NavigationApi.getNavigator( this, new NavigationApi.NavigatorListener() { /** Sets up the navigation UI when the navigator is ready for use. */ @Override public void onNavigatorReady(Navigator navigator) { displayMessage("Navigator ready."); mNavigator = navigator; mNavFragment = (SupportNavigationFragment) getSupportFragmentManager().findFragmentById(R.id.navigation_fragment); // Get the map. mNavFragment.getMapAsync( new OnMapReadyCallback() { @Override public void onMapReady(GoogleMap map) { mMap = map; // Navigate to a place, specified by Place ID. navigateToPlace(SYDNEY_OPERA_HOUSE); } }); } /** * Handles errors from the Navigation SDK. * * @param errorCode The error code returned by the navigator. */ @Override public void onError(@NavigationApi.ErrorCode int errorCode) { switch (errorCode) { case NavigationApi.ErrorCode.NOT_AUTHORIZED: displayMessage( "Error loading Navigation SDK: Your API key is " + "invalid or not authorized to use the Navigation SDK."); break; case NavigationApi.ErrorCode.TERMS_NOT_ACCEPTED: displayMessage( "Error loading Navigation SDK: User did not accept " + "the Navigation Terms of Use."); break; case NavigationApi.ErrorCode.NETWORK_ERROR: displayMessage("Error loading Navigation SDK: Network error."); break; case NavigationApi.ErrorCode.LOCATION_PERMISSION_MISSING: displayMessage( "Error loading Navigation SDK: Location permission " + "is missing."); break; default: displayMessage("Error loading Navigation SDK: " + errorCode); } } }); } /** Customizes the navigation UI and the map. */ private void customizeNavigationUI() { // Set custom colors for the navigator. mNavFragment.setStylingOptions( new StylingOptions() .primaryDayModeThemeColor(0xff1A237E) .secondaryDayModeThemeColor(0xff3F51B5) .primaryNightModeThemeColor(0xff212121) .secondaryNightModeThemeColor(0xff424242) .headerLargeManeuverIconColor(0xffffff00) .headerSmallManeuverIconColor(0xffffa500) .headerNextStepTypefacePath("/system/fonts/NotoSerif-BoldItalic.ttf") .headerNextStepTextColor(0xff00ff00) .headerNextStepTextSize(20f) .headerDistanceTypefacePath("/system/fonts/NotoSerif-Italic.ttf") .headerDistanceValueTextColor(0xff00ff00) .headerDistanceUnitsTextColor(0xff0000ff) .headerDistanceValueTextSize(20f) .headerDistanceUnitsTextSize(18f) .headerInstructionsTypefacePath("/system/fonts/NotoSerif-BoldItalic.ttf") .headerInstructionsTextColor(0xffffff00) .headerInstructionsFirstRowTextSize(24f) .headerInstructionsSecondRowTextSize(20f) .headerGuidanceRecommendedLaneColor(0xffffa500)); mMap.setTrafficEnabled(false); // Place a marker at the final destination. if (mNavigator.getCurrentRouteSegment() != null) { LatLng destinationLatLng = mNavigator.getCurrentRouteSegment().getDestinationLatLng(); Bitmap destinationMarkerIcon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_person_pin_48dp); mMap.addMarker( new MarkerOptions() .position(destinationLatLng) .icon(BitmapDescriptorFactory.fromBitmap(destinationMarkerIcon)) .title("Destination marker")); // Listen for a tap on the marker. mMap.setOnMarkerClickListener( new GoogleMap.OnMarkerClickListener() { @Override public boolean onMarkerClick(Marker marker) { displayMessage( "Marker tapped: " + marker.getTitle() + ", at location " + marker.getPosition().latitude + ", " + marker.getPosition().longitude); // The event has been handled. return true; } }); } // Set the camera to follow the device location with 'TILTED' driving view. mMap.followMyLocation(CameraPerspective.TILTED); } /** * Requests directions from the user's current location to a specific place (provided by the * Google Places API). */ private void navigateToPlace(String placeId) { Waypoint destination; try { destination = Waypoint.builder().setPlaceIdString(placeId).build(); } catch (Waypoint.UnsupportedPlaceIdException e) { displayMessage("Error starting navigation: Place ID is not supported."); return; } // Create a future to await the result of the asynchronous navigator task. ListenableResultFuture<Navigator.RouteStatus> pendingRoute = mNavigator.setDestination(destination); // Define the action to perform when the SDK has determined the route. pendingRoute.setOnResultListener( new ListenableResultFuture.OnResultListener<Navigator.RouteStatus>() { @Override public void onResult(Navigator.RouteStatus code) { switch (code) { case OK: // Hide the toolbar to maximize the navigation UI. if (getActionBar() != null) { getActionBar().hide(); } // Customize the navigation UI. customizeNavigationUI(); // Enable voice audio guidance (through the device speaker). mNavigator.setAudioGuidance(Navigator.AudioGuidance.VOICE_ALERTS_AND_GUIDANCE); // Simulate vehicle progress along the route for demo/debug builds. if (BuildConfig.DEBUG) { mNavigator .getSimulator() .simulateLocationsAlongExistingRoute( new SimulationOptions().speedMultiplier(5)); } // Start turn-by-turn guidance along the current route. mNavigator.startGuidance(); break; // Handle error conditions returned by the navigator. case NO_ROUTE_FOUND: displayMessage("Error starting navigation: No route found."); break; case NETWORK_ERROR: displayMessage("Error starting navigation: Network error."); break; case ROUTE_CANCELED: displayMessage("Error starting navigation: Route canceled."); break; default: displayMessage("Error starting navigation: " + String.valueOf(code)); } } }); } /** Handles the result of the request for location permissions. */ @Override public void onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { mLocationPermissionGranted = false; switch (requestCode) { case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: { // If request is canceled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { mLocationPermissionGranted = true; } } } } /** * Shows a message on screen and in the log. Used when something goes wrong. * * @param errorMessage The message to display. */ private void displayMessage(String errorMessage) { Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); Log.d(TAG, errorMessage); } }
修改導覽標頭
使用 SupportNavigationFragment.setStylingOptions()
或 NavigationView.setStylingOptions()
變更導航標頭的佈景主題,以及標頭下方顯示的下一個轉彎指標 (如有)。
您可以設定下列屬性:
屬性類型 | 屬性 |
---|---|
背景顏色 |
|
說明文字的文字元素 |
|
後續步驟的文字元素 |
|
操控圖示 |
|
車道指引 |
|
以下範例說明如何設定樣式選項:
private SupportNavigationFragment mNavFragment;
mNavFragment = (SupportNavigationFragment) getFragmentManager()
.findFragmentById(R.id.navigation_fragment);
// Set the styling options on the fragment.
mNavFragment.setStylingOptions(new StylingOptions()
.primaryDayModeThemeColor(0xff1A237E)
.secondaryDayModeThemeColor(0xff3F51B5)
.primaryNightModeThemeColor(0xff212121)
.secondaryNightModeThemeColor(0xff424242)
.headerLargeManeuverIconColor(0xffffff00)
.headerSmallManeuverIconColor(0xffffa500)
.headerNextStepTypefacePath("/system/fonts/NotoSerif-BoldItalic.ttf")
.headerNextStepTextColor(0xff00ff00)
.headerNextStepTextSize(20f)
.headerDistanceTypefacePath("/system/fonts/NotoSerif-Italic.ttf")
.headerDistanceValueTextColor(0xff00ff00)
.headerDistanceUnitsTextColor(0xff0000ff)
.headerDistanceValueTextSize(20f)
.headerDistanceUnitsTextSize(18f)
.headerInstructionsTypefacePath("/system/fonts/NotoSerif-BoldItalic.ttf")
.headerInstructionsTextColor(0xffffff00)
.headerInstructionsFirstRowTextSize(24f)
.headerInstructionsSecondRowTextSize(20f)
.headerGuidanceRecommendedLaneColor(0xffffa500));
關閉車流量圖層
使用 GoogleMap.setTrafficEnabled()
可啟用或停用地圖上的交通圖層。這項設定會影響地圖上顯示的整體車流量密度指標。不過,這不會影響導航器繪製路線時顯示的交通資訊。
private GoogleMap mMap;
// Get the map, and when the async call returns, setTrafficEnabled
// (callback will be on the UI thread)
mMap = mNavFragment.getMapAsync(navMap -> navMap.setTrafficEnabled(false));
啟用紅綠燈和停車標誌
您可以在導航期間啟用地圖中的紅綠燈和停車標誌顯示功能,為路線和行程機動提供額外的背景資訊。
根據預設,Navigation SDK 會停用交通號誌和停車標誌。如要啟用這項功能,請為每個地圖項目個別呼叫 DisplayOptions
。
DisplayOptions displayOptions =
new DisplayOptions().showTrafficLights(true).showStopSigns(true);
新增自訂標記
Navigation SDK for Android 現在會使用 Google 地圖 API 做為標記。詳情請參閱 Maps API 說明文件。
浮動文字
您可以在應用程式的任何位置新增浮動文字,但不得覆蓋 Google 歸屬資訊。Navigation SDK 不支援將文字錨定至地圖上的經緯度或標籤。詳情請參閱「資訊視窗」。
顯示速限
您可以透過程式輔助方式顯示或隱藏速限圖示。使用 NavigationView.setSpeedLimitIconEnabled()
或 SupportNavigationFragment.setSpeedLimitIconEnabled()
圖示顯示或隱藏速限圖示。啟用後,系統會在導航期間於底角顯示速限圖示。這個圖示會顯示車輛行經道路的速限。這個圖示只會顯示在可取得可靠速限資料的位置。
// Display the Speed Limit icon
mNavFragment.setSpeedLimitIconEnabled(true);
當最近使用者按鈕顯示時,速限圖示會暫時隱藏。
設定夜間模式
您可以透過程式設計控制夜間模式的行為。使用 NavigationView.setForceNightMode()
或 SupportNavigationFragment.setForceNightMode()
開啟或關閉夜間模式,或是讓 Navigation SDK for Android 控制夜間模式。
AUTO
讓 Navigation SDK 根據裝置位置和當地時間判斷適當的模式。FORCE_NIGHT
會強制開啟夜間模式。FORCE_DAY
會強制開啟日間模式。
以下範例說明如何強制在導覽片段中開啟夜間模式:
// Force night mode on.
mNavFragment.setForceNightMode(FORCE_NIGHT);
顯示路線清單
首先,請建立檢視畫面並將其新增至階層。
void setupDirectionsListView() {
// Create the view.
DirectionsListView directionsListView = new DirectionsListView(getApplicationContext());
// Add the view to your view hierarchy.
ViewGroup group = findViewById(R.id.directions_view);
group.addView(directionsListView);
// Add a button to your layout to close the directions list view.
ImageButton button = findViewById(R.id.close_directions_button); // this button is part of the container we hide in the next line.
button.setOnClickListener(
v -> findViewById(R.id.directions_view_container).setVisibility(View.GONE));
}
請務必將生命週期事件轉送至 DirectionsListView
,就像使用 NavigationView
一樣。例如:
protected void onResume() {
super.onResume();
directionsListView.onResume();
}
隱藏替代路線
如果使用者介面因資訊過多而顯得雜亂,您可以減少顯示的替代路線數量 (比預設值少),或是完全不顯示替代路線,以減少雜亂感。您可以在擷取路線之前設定這個選項,方法是呼叫 RoutingOptions.alternateRoutesStrategy()
方法,並使用下列其中一個列舉值:
列舉值 | 說明 |
---|---|
AlternateRoutesStrategy.SHOW_ALL | 預設。最多顯示兩個替代路線。 |
AlternateRoutesStrategy.SHOW_ONE | 顯示一個替代路線 (如有)。 |
AlternateRoutesStrategy.SHOW_NONE | 隱藏替代路線。 |
以下程式碼範例說明如何完全隱藏替代路線。
RoutingOptions routingOptions = new RoutingOptions();
routingOptions.alternateRoutesStrategy(AlternateRoutesStrategy.SHOW_NONE);
navigator.setDestinations(destinations, routingOptions, displayOptions);
行程進度列
行程進度列是指在導航開始時,在地圖右側邊緣顯示的垂直列。啟用後,系統會顯示整趟行程的概覽,以及使用者的目的地和目前位置。
使用者不必放大,即可快速預測任何即將發生的問題,例如流量。必要時,他們可以重新安排行程。如果使用者重新安排行程,進度列就會重設,就像從該點開始新的行程一樣。
行程進度列會顯示下列狀態指標:
Route elapsed:行程經過的時間。
目前位置:使用者在行程中目前的位置。
路況狀態:即將發生的交通狀況。
最終目的地:行程的最終目的地。
在 NavigationView 或 SupportNavigationFragment 上呼叫 setTripProgressBarEnabled()
方法,啟用行程進度列。例如:
// Enable the trip progress bar.
mNavFragment.setTripProgressBarEnabled(true);