Com o SDK Navigation para Android, é possível modificar a experiência do usuário com o mapa determinando quais elementos e controles integrados da interface aparecem no mapa. Você também pode ajustar a aparência visual da interface de navegação. Consulte a página de políticas para orientações sobre modificações aceitáveis na interface de navegação.
Neste documento, descrevemos como modificar a interface do usuário do mapa de duas maneiras:
Controles da interface do mapa
Os controles da interface do mapa são a maneira recomendada de colocar elementos personalizados na visualização de navegação para garantir o posicionamento correto. Quando o layout integrado muda,
o SDK de navegação para Android reposiciona automaticamente os controles personalizados. É possível definir uma visualização de controle personalizada por vez
para cada posição. Se o design exigir vários elementos da interface, coloque-os
em um ViewGroup
e transmita-o ao método setCustomControl
.
O método
setCustomControl
fornece posições conforme definido no tipo enumerado
CustomControlPosition
:
SECONDARY_HEADER
(aparece apenas no modo retrato)BOTTOM_START_BELOW
BOTTOM_END_BELOW
FOOTER


Adicionar um controle personalizado
- Crie uma visualização Android com o elemento de interface ou ViewGroup personalizado.
- Infle o XML ou instancie a visualização personalizada para receber uma instância da visualização.
Use
NavigationView.setCustomControl
ouSupportNavigationFragment.setCustomControl
com a posição de controle personalizada escolhida do tipo enumeradoCustomControlPosition
.O exemplo abaixo cria um fragmento e adiciona um controle personalizado na posição do cabeçalho secundário.
mNavFragment.setCustomControl(getLayoutInflater(). inflate(R.layout.your_custom_control, null), CustomControlPosition.SECONDARY_HEADER); ```
Remover um controle personalizado
Para remover um controle personalizado, chame o
método setCustomControl
com um parâmetro de visualização null
e a posição do controle personalizado escolhida.
Por exemplo, o snippet a seguir remove qualquer cabeçalho secundário personalizado e retorna ao conteúdo padrão:
mNavFragment.setCustomControl(null, CustomControlPosition.SECONDARY_HEADER);
Posições de controle personalizadas
Cabeçalho secundário

Para usar essa posição de controle personalizada, transmita a posição CustomControlPosition.SECONDARY_HEADER
para setCustomControl
.
Por padrão, os layouts de tela no modo de navegação oferecem uma posição para um cabeçalho secundário localizado abaixo do cabeçalho principal. Esse cabeçalho secundário aparece quando necessário, como com a orientação de faixa. O app pode usar essa posição de cabeçalho secundário do layout para conteúdo personalizado. Quando você usa esse recurso, o controle abrange qualquer conteúdo de cabeçalho secundário padrão. Se a visualização de navegação tiver um plano de fundo, ele vai permanecer no lugar, coberto pelo cabeçalho secundário. Quando o app remove o controle personalizado, qualquer cabeçalho secundário padrão pode aparecer no lugar dele.
A posição do cabeçalho secundário personalizado alinha a borda superior com a borda inferior do
cabeçalho principal. Essa posição só é aceita em portrait mode
. Em
landscape mode
, o cabeçalho secundário fica indisponível, e o layout não
muda.
Começar de baixo


Para usar essa posição de controle personalizada, transmita a posição CustomControlPosition.BOTTOM_START_BELOW
para setCustomControl
.
Essa posição de controle personalizada fica no canto inferior esquerdo do mapa. Em portrait mode
e landscape mode
, ele fica acima do card de ETA e/ou do rodapé personalizado (ou na parte de baixo do mapa se nenhum deles estiver presente), e os elementos do Nav SDK, incluindo o botão de recentralização e o logotipo do Google, são movidos para cima para considerar a altura da visualização de controle personalizada. Esse controle é posicionado dentro dos limites visíveis do mapa. Portanto, qualquer preenchimento adicionado às bordas inferior ou inicial do mapa também muda a posição desse controle.
Extremidade inferior


Para usar essa posição de controle personalizada, transmita a posição CustomControlPosition.BOTTOM_END_BELOW
para setCustomControl
.
Essa posição de controle personalizado fica no canto inferior do mapa. Em portrait mode
, ela fica acima do card de HEC e/ou do rodapé personalizado (ou na parte de baixo do mapa se nenhum deles estiver presente), mas em landscape mode
, ela fica alinhada com a parte de baixo do mapa. Todos os elementos do SDK de navegação que aparecem ao longo do lado final (lado direito em LTR) são movidos para cima para considerar a altura da visualização de controle personalizada. Esse controle é posicionado dentro dos limites visíveis do mapa. Portanto, qualquer preenchimento adicionado às bordas inferior ou final do mapa também muda a posição desse controle.
Footer


Para usar essa posição de controle personalizada, transmita a posição CustomControlPosition.FOOTER
para setCustomControl
.
Essa posição de controle personalizada é projetada para uma visualização de rodapé personalizada. Se o card de HEC do SDK do Nav estiver visível, esse controle vai ficar acima dele. Caso contrário, o controle será alinhado à parte de baixo do mapa. Diferente dos controles personalizados BOTTOM_START_BELOW
e BOTTOM_END_BELOW
, esse controle é posicionado fora dos limites visíveis do mapa, o que significa que qualquer padding adicionado ao mapa não vai mudar a posição dele.
Em portrait mode
, o rodapé personalizado tem a largura total. Os controles personalizados nas posições CustomControlPosition.BOTTOM_START_BELOW
e CustomControlPosition.BOTTOM_END_BELOW
, bem como os elementos da IU do SDK de navegação, como o botão de restauração e o logotipo do Google, são posicionados acima do rodapé do controle personalizado. A posição padrão do ícone de seta para cima considera a altura personalizada do rodapé.
Em landscape mode
, o rodapé personalizado tem metade da largura e está alinhado ao lado inicial (lado esquerdo em LTR), assim como o card de ETA do SDK do Nav. Os controles personalizados na posição CustomControlPosition.BOTTOM_START_BELOW
e os elementos da interface do SDK de navegação, como o botão de restauração e o logotipo do Google, são posicionados acima do rodapé do controle personalizado. Os controles personalizados na posição CustomControlPosition.BOTTOM_END_BELOW
e todos os elementos da interface do SDK de navegação ao longo do lado final (lado direito em LTR) permanecem alinhados com a parte de baixo do mapa. A posição padrão do chevron não muda quando um rodapé personalizado está presente, porque ele não se estende até o final do mapa.
Os controles personalizados nas posições CustomControlPosition.BOTTOM_START_BELOW
e CustomControlPosition.BOTTOM_END_BELOW
, bem como os elementos da IU do SDK de navegação, como o botão de restauração e o logotipo do Google, são posicionados acima do rodapé do controle personalizado.
Acessórios da interface do mapa
O SDK do Navigation para Android oferece acessórios de interface que aparecem durante a navegação, semelhantes aos encontrados no aplicativo Google Maps para Android. É possível ajustar a visibilidade ou a aparência desses controles, conforme descrito nesta seção. As mudanças feitas aqui são refletidas na próxima sessão de navegação.
Consulte a página de políticas para ver diretrizes sobre modificações aceitáveis na interface de navegação.
Ver o código
Mostre/oculte o código Java para a atividade de navegação.
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); } }
Modificar o cabeçalho de navegação
Use
SupportNavigationFragment.setStylingOptions()
ou
NavigationView.setStylingOptions()
para mudar o tema do cabeçalho de navegação e o indicador da próxima curva que
aparece abaixo do cabeçalho quando disponível.
É possível definir os seguintes atributos:
Tipo de atributo | Atributos |
---|---|
Cor do plano de fundo |
|
Elementos de texto para instruções |
|
Elementos de texto para as próximas etapas |
|
Ícones de manobra |
|
Indicação de faixa |
|
O exemplo a seguir mostra como definir opções de estilo:
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));
Desativar a camada de trânsito
Use
GoogleMap.setTrafficEnabled()
para ativar ou desativar a camada de tráfego no mapa. Essa configuração afeta as indicações de densidade de tráfego mostradas no mapa como um todo. No entanto, isso não
afeta as indicações de trânsito no trajeto traçado pelo navegador.
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));
Ativar semáforos e sinais de pare
É possível ativar a exibição de semáforos e sinais de parada no mapa durante a navegação ativa, o que fornece mais contexto para rotas e manobras de viagem.
Por padrão, os semáforos e sinais de parada estão desativados no
SDK de navegação. Para ativar esse recurso, chame
DisplayOptions
para cada recurso individualmente.
DisplayOptions displayOptions =
new DisplayOptions().showTrafficLights(true).showStopSigns(true);
Adicionar marcadores personalizados
O SDK do Navigation para Android agora usa as APIs do Google Maps para marcadores. Acesse a documentação da API Maps para mais informações.
Texto flutuante
Você pode adicionar texto flutuante em qualquer lugar do app, desde que ele não cubra a atribuição do Google. O SDK de navegação não oferece suporte à ancoragem do texto a uma latitude/longitude no mapa ou a um rótulo. Acesse Janelas de informações para mais informações.
Mostrar o limite de velocidade
É possível mostrar ou ocultar o ícone de limite de velocidade de maneira programática. Use
NavigationView.setSpeedLimitIconEnabled()
ou
SupportNavigationFragment.setSpeedLimitIconEnabled()
para mostrar ou ocultar o ícone do limite de velocidade. Quando ativado, o ícone do limite de velocidade
é mostrado em um canto inferior durante a orientação. O ícone mostra o limite de velocidade
da via em que o veículo está trafegando. O ícone só aparece em locais
em que dados confiáveis de limite de velocidade estão disponíveis.
// Display the Speed Limit icon
mNavFragment.setSpeedLimitIconEnabled(true);
O ícone de limite de velocidade fica oculto temporariamente quando o botão de restauração é exibido.
Definir o modo noturno
É possível controlar o comportamento do modo noturno de maneira programática. Use
NavigationView.setForceNightMode()
ou
SupportNavigationFragment.setForceNightMode()
para ativar ou desativar o modo noturno ou deixe que o SDK do Navigation para Android
controle.
AUTO
Permite que o SDK de navegação determine o modo apropriado de acordo com a localização do dispositivo e o horário local.FORCE_NIGHT
ativa o Modo noturno.FORCE_DAY
ativa o modo dia.
O exemplo a seguir mostra como forçar o modo noturno a ser ativado em um fragmento de navegação:
// Force night mode on.
mNavFragment.setForceNightMode(FORCE_NIGHT);
Mostrar lista de rotas
Primeiro, crie a visualização e adicione à hierarquia.
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));
}
Encaminhe os eventos de ciclo de vida para o DirectionsListView
, assim como
são com NavigationView
. Exemplo:
protected void onResume() {
super.onResume();
directionsListView.onResume();
}
Como ocultar trajetos alternativos
Quando a interface do usuário fica sobrecarregada com muitas informações, é possível
reduzir a desordem mostrando menos rotas alternativas do que o padrão (duas) ou
não mostrando nenhuma rota alternativa. É possível configurar essa opção antes de buscar as rotas chamando o método RoutingOptions.alternateRoutesStrategy()
com um dos seguintes valores de enumeração:
Valor de enumeração | Descrição |
---|---|
AlternateRoutesStrategy.SHOW_ALL | Padrão. Mostra até duas rotas alternativas. |
AlternateRoutesStrategy.SHOW_ONE | Mostra uma rota alternativa (se disponível). |
AlternateRoutesStrategy.SHOW_NONE | Oculta as rotas alternativas. |
O exemplo de código abaixo demonstra como ocultar rotas alternativas.
RoutingOptions routingOptions = new RoutingOptions();
routingOptions.alternateRoutesStrategy(AlternateRoutesStrategy.SHOW_NONE);
navigator.setDestinations(destinations, routingOptions, displayOptions);
Barra de progresso da viagem
A barra de progresso da viagem é uma barra vertical que aparece no canto direito do mapa quando a navegação começa. Quando ativado, ele mostra uma visão geral de uma viagem inteira, além do destino e da posição atual do usuário.
Isso permite que os usuários antecipem rapidamente qualquer problema futuro, como tráfego, sem precisar dar zoom. Eles podem redirecionar a viagem, se necessário. Se o usuário mudar o trajeto, a barra de progresso será redefinida como se uma nova viagem tivesse começado a partir desse ponto.
A barra de progresso da viagem mostra os seguintes indicadores de status:
Tempo da rota: o tempo da viagem.
Posição atual: a localização atual do usuário na viagem.
Status do trânsito: o status do trânsito que está por vir.
Destino final: o destino final da viagem.
Ative a barra de progresso da viagem chamando o método setTripProgressBarEnabled()
na
NavigationView
ou
SupportNavigationFragment.
Exemplo:
// Enable the trip progress bar.
mNavFragment.setTripProgressBarEnabled(true);