利用 Navigation SDK for Android,您可以确定将哪些内置界面控件和元素显示在地图上,从而修改用户与地图的互动体验。您还可以调整导航界面的视觉外观。如需了解有关对导航界面进行可接受修改的准则,请参阅“政策”页面。
本文档介绍了如何通过以下两种方式修改地图的界面:
地图界面控件
如需在导航视图上放置自定义界面元素,请使用地图界面控件进行正确定位。当内置布局发生变化时,Navigation SDK for Android 会自动重新定位您的自定义控件。您可以为每个位置一次设置一个自定义控制视图。如果您的设计需要多个界面元素,您可以将它们放置在 ViewGroup
中,然后将其传递给 setCustomControl
方法。
setCustomControl
方法提供的位置如 CustomControlPosition
枚举中所定义:
SECONDARY_HEADER
(仅在竖屏模式下显示)BOTTOM_START_BELOW
BOTTOM_END_BELOW
FOOTER


添加自定义控件
- 使用自定义界面元素或 ViewGroup 创建 Android View。
- 膨胀 XML 或实例化自定义视图以获取视图实例。
将
NavigationView.setCustomControl
或SupportNavigationFragment.setCustomControl
与从CustomControlPosition
枚举中选择的自定义控件位置搭配使用。以下示例创建了一个 fragment,并在次要标题位置添加了一个自定义控件。
mNavFragment.setCustomControl(getLayoutInflater(). inflate(R.layout.your_custom_control, null), CustomControlPosition.SECONDARY_HEADER); ```
移除自定义控件
如需移除自定义控件,请调用 setCustomControl
方法,并提供 null
视图参数和所选的自定义控件位置。
例如,以下代码段会移除所有自定义的次要标题,并返回到默认内容:
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
中,它与地图底部对齐。沿末端侧(从左向右的布局中为右侧)显示的任何 Nav SDK 元素都会向上移动,以适应自定义控件视图的高度。此控件位于可见地图边界内,因此添加到地图底部或末端边缘的任何内边距也会改变此控件的位置。
页脚


如需使用此自定义控件位置,请将位置 CustomControlPosition.FOOTER
传递给 setCustomControl
。
此自定义控制位置专为自定义页脚视图而设计。如果导航 SDK 预计到达时间卡片可见,此控件会显示在其上方。否则,控件会与地图底部对齐。与 BOTTOM_START_BELOW
和 BOTTOM_END_BELOW
自定义控件不同,此控件位于可见地图边界之外,这意味着添加到地图的任何内边距都不会改变此控件的位置。
在 portrait mode
中,自定义页脚的宽度为全宽。CustomControlPosition.BOTTOM_START_BELOW
和 CustomControlPosition.BOTTOM_END_BELOW
位置的自定义控件以及 Nav SDK 界面元素(例如重新居中按钮和 Google 徽标)都位于自定义控件页脚上方。chevron 的默认位置会考虑自定义页脚高度。
在 landscape mode
中,自定义页脚的宽度为一半,并与起始侧(从左向右的语言中的左侧)对齐,就像导航 SDK 的 ETA 卡片一样。CustomControlPosition.BOTTOM_START_BELOW
位置的自定义控件和 Nav SDK 界面元素(例如重新居中按钮和 Google 徽标)位于自定义控件页脚上方。位于 CustomControlPosition.BOTTOM_END_BELOW
位置的自定义控件以及沿结束侧(在 LTR 中为右侧)放置的任何 Nav SDK 界面元素都与地图底部保持对齐。当存在自定义页脚时,由于页脚不会延伸到地图的末端,因此人字形的默认位置不会发生变化。
位于 CustomControlPosition.BOTTOM_START_BELOW
和 CustomControlPosition.BOTTOM_END_BELOW
位置的自定义控件以及 Nav SDK 界面元素(例如重新居中按钮和 Google 徽标)位于自定义控件页脚上方。
地图界面配件
Navigation SDK for Android 提供的界面配件与 Google 地图 Android 应用中提供的配件类似,会在导航期间显示。 您可以按照本部分中的说明调整这些控件的可见性或视觉外观。您在此处所做的更改会在下次导航会话期间反映出来。
如需了解有关可接受的导航界面修改的指南,请参阅“政策”页面。
查看代码
显示/隐藏导航 activity 的 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); } }
目的地突出显示和入口
如果目的地是使用 placeID
创建的,系统会尽可能突出显示目的地建筑物并显示入口图标。这些视觉提示有助于用户区分目的地并前往目的地。
如需创建具有 placeID
的目的地,请使用 Waypoint.Builder.setPlaceIdString()
方法。
修改导航标头
使用 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 Maps 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
强制开启日间模式。
以下示例展示了如何在导航 fragment 中强制开启夜间模式:
// 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));
}
请务必像使用 NavigationView
一样,将生命周期事件转发给 DirectionsListView
。例如:
protected void onResume() {
super.onResume();
directionsListView.onResume();
}
隐藏备选路线
如果界面上显示的信息过多,导致界面杂乱无章,您可以减少显示的备选路线(少于默认的两条),或者完全不显示备选路线,从而减少杂乱程度。您可以在提取路线之前配置此选项,方法是使用以下某个枚举值调用 RoutingOptions.alternateRoutesStrategy()
方法:
枚举值 | 说明 |
---|---|
AlternateRoutesStrategy.SHOW_ALL | 默认值。显示最多两条备选路线。 |
AlternateRoutesStrategy.SHOW_ONE | 显示一条备选路线(如果有)。 |
AlternateRoutesStrategy.SHOW_NONE | Hides alternate routes.(隐藏备选路线。) |
以下代码示例演示了如何完全隐藏替代路线。
RoutingOptions routingOptions = new RoutingOptions();
routingOptions.alternateRoutesStrategy(AlternateRoutesStrategy.SHOW_NONE);
navigator.setDestinations(destinations, routingOptions, displayOptions);
行程进度条
行程进度条是一条竖线,在导航开始时显示在地图的起始/前导侧。启用后,系统会显示整个行程的概览,以及用户的目的地和当前位置。
这样一来,用户无需放大即可快速预测即将出现的任何问题,例如交通状况。然后,他们可以根据需要重新规划行程。如果用户重新规划行程,进度条会重置,就像从该点开始了一段新行程一样。
行程进度条会显示以下状态指示器:
路线已过 - 行程中已过的时间。
当前位置 - 用户在行程中的当前位置。
路况信息 - 即将到来的路况。
最终目的地 - 行程的最终目的地。
通过对 NavigationView 或 SupportNavigationFragment 调用 setTripProgressBarEnabled()
方法来启用行程进度条。
例如:
// Enable the trip progress bar.
mNavFragment.setTripProgressBarEnabled(true);
行程进度条位置
- 该栏的左侧大致与车速表、Google 徽标和“重新居中”按钮(显示时)的左侧对齐。宽度为 12 dp。
- 行程进度条的高度保持不变。为了适应较小设备中的垂直空间限制,行程进度条的可见性和高度会根据指定的屏幕高度断点进行调整。这些断点与设备方向以及地图在屏幕上实际占据的空间无关:
- 如果行程进度条与转向卡或其他导航界面元素重叠,则会显示在这些其他元素下方。
屏幕高度 | 行程进度条可见性 | 行程进度条高度 | 行程进度条 y 轴位置 |
---|---|---|---|
小:0 dp - 551 dp | 不显示 | 不适用 | 不适用 |
中:552 dp - 739 dp | 可见 | 130 dp | 开始侧上方的控件(速度表 / Google 徽标 / 重新居中按钮) |
大:740 dp 及以上 | 可见 | 290 dp | 开始侧上方的控件(速度表 / Google 徽标 / 重新居中按钮) |
Prompt Visibility API(实验性)
借助提示可见性 API,您可以通过添加监听器来避免 Navigation SDK 生成的界面元素与您自己的自定义界面元素发生冲突。该监听器会在 Navigation SDK 界面元素即将显示之前以及该元素被移除后立即收到回调。如需了解详情(包括代码示例),请参阅配置实时中断页面中的提示可见性 API 部分。