Selecionar o lugar atual e mostrar detalhes em um mapa

Neste tutorial, mostramos como encontrar a localização atual de um dispositivo Android e exibir detalhes do lugar, como um ponto de interesse comercial ou de outros tipos. Siga este tutorial se você quiser criar um app para Android usando o Maps SDK for Android, o Places SDK for Android e o provedor de localização combinada nas APIs Location do Google Play Services.

Acessar o código

Clone ou faça o download do repositório de exemplos da API Google Maps Android v2 do GitHub.

Configurar seu projeto de desenvolvimento

Siga estas etapas para criar o projeto de tutorial no Android Studio.

  1. Faça o download do Android Studio e instale-o.
  2. Adicione o pacote Google Play Services ao Android Studio.
  3. Clone ou faça o download do repositório de exemplos da API Google Maps Android v2, caso ainda não tenha feito isso no início do tutorial.
  4. Importe o projeto de tutorial:

    • No Android Studio, selecione File > New > Import Project.
    • Acesse o local onde você salvou o repositório de exemplos da API Google Maps Android v2 após fazer o download.
    • Encontre o projeto CurrentPlaceDetailsOnMap neste local:
      PATH-TO-SAVED-REPO/android-samples/tutorials/CurrentPlaceDetailsOnMap
    • Selecione o diretório do projeto e clique em OK. O Android Studio criará seu projeto usando a ferramenta Gradle.

Receber uma chave de API e ativar as APIs necessárias

Para acompanhar este tutorial, você precisa de uma chave de API do Google que possa usar o Maps SDK for Android e o Places SDK for Android.

Clique no botão abaixo para a receber uma chave e ativar as APIs.

Primeiros passos

Se você quiser mais detalhes, consulte o guia completo sobre como acessar uma chave de API.

Adicionar a chave de API ao seu app

  1. Edite o arquivo gradle.properties do projeto.
  2. Cole sua chave de API no valor da propriedade GOOGLE_MAPS_API_KEY.

    GOOGLE_MAPS_API_KEY=PASTE-YOUR-API-KEY-HERE

    Quando você cria o app, o Gradle copia essa chave no manifesto do Android. O arquivo build.gradle do aplicativo contém a linha a seguir, que mapeia a string google_maps_key no manifesto para a propriedade GOOGLE_MAPS_API_KEY do Gradle:

    resValue "string", "google_maps_key",
            (project.findProperty("GOOGLE_MAPS_API_KEY") ?: "")
    

Criar e executar seu app

  1. Conecte um dispositivo Android ao computador. Siga as instruções se você quiser ativar as opções do desenvolvedor no seu dispositivo Android e permitir que o sistema detecte o aparelho. Também é possível usar o gerenciador de AVD para configurar um dispositivo virtual. Ao escolher um emulador, selecione uma imagem que inclua as APIs do Google. Para mais detalhes, consulte o guia de primeiros passos.
  2. No Android Studio, clique na opção de menu Run ou no ícone do botão de reprodução. Escolha um dispositivo quando solicitado.

O Android Studio invoca o Gradle para criar o app e, em seguida, executa o aplicativo no aparelho ou no emulador. Será exibido um mapa com vários marcadores centralizados ao redor do seu local atual, semelhante à imagem desta página.

Solução de problemas:

  • Se o mapa não aparecer, confirme se você recebeu uma chave de API e a incluiu no app, como descrito acima. Verifique se há mensagens de erro sobre essa chave no registro do Android Monitor do Android Studio.
  • Se o mapa exibir apenas um marcador na Ponte da Baía de Sydney (o local padrão especificado no app), verifique se você concedeu a permissão de localização ao aplicativo. O app solicita essa permissão durante o tempo de execução, de acordo com o padrão descrito no guia de permissões do Android. Também é possível definir permissões diretamente no dispositivo. Para isso, escolha Configurações > Apps > nome do aplicativo > Permissões > Localização. Se você quiser saber como lidar com permissões no seu código, consulte o guia abaixo para solicitar a permissão de localização no seu app.
  • Use as ferramentas de depuração do Android Studio para ver os registros e depurar o app.

Entender o código

Nesta parte do tutorial, explicamos as partes mais importantes do app CurrentPlaceDetailsOnMap para ajudar você a criar um aplicativo semelhante.

Instanciar os clientes da API Places

As interfaces a seguir oferecem os principais pontos de entrada do Places SDK for Android:

  • A GeoDataClient dá acesso ao banco de dados do Google, que tem informações sobre lugares e empresas.
  • A PlaceDetectionClient concede acesso rápido ao lugar atual do dispositivo e informa a localização do aparelho.

A interface LocationServices é o principal ponto de entrada para os Serviços de localização do Android.

Para usar as APIs, instancie GeoDataClient, PlaceDetectionClient e FusedLocationProviderClient no método onCreate() do seu fragmento ou da atividade, conforme a amostra de código a seguir:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Construct a GeoDataClient.
    mGeoDataClient = Places.getGeoDataClient(this, null);

    // Construct a PlaceDetectionClient.
    mPlaceDetectionClient = Places.getPlaceDetectionClient(this, null);

    // Construct a FusedLocationProviderClient.
    mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
}

Solicitar permissão de localização

Seu app precisa solicitar permissão de localização para determinar o lugar do dispositivo e permitir que o usuário toque no botão Meu local do mapa.

Neste tutorial, mostramos o código necessário para solicitar a permissão de localização precisa. Se você quiser mais detalhes, consulte o guia sobre permissões do Android.

  1. Adicione a permissão como um elemento filho de <manifest> no seu manifesto do Android:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.currentplacedetailsonmap">
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    </manifest>
    
  2. Solicite permissões de tempo de execução no seu app. Com isso, o usuário poderá conceder ou negar o compartilhamento de localização. O código a seguir verifica se o usuário atribuiu a permissão exata. Em caso negativo, uma solicitação é exibida:

    private void getLocationPermission() {
        /*
         * 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);
        }
    }
    
  3. Modifique o callback onRequestPermissionsResult() para lidar com o resultado da solicitação de permissão:

    @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 cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mLocationPermissionGranted = true;
                }
            }
        }
        updateLocationUI();
    }
    

    Posteriormente neste tutorial, descreveremos o método updateLocationUI().

Adicionar um mapa

Exiba um mapa usando o Maps SDK for Android.

  1. Adicione um elemento <fragment> ao arquivo de layout da sua atividade, activity_maps.xml. Esse elemento define um SupportMapFragment para atuar como contêiner do mapa e conceder acesso ao objeto GoogleMap. Neste tutorial, usamos a versão do fragmento de mapa da Biblioteca de Suporte do Android para oferecer suporte às versões anteriores do framework Android.

    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.example.currentplacedetailsonmap.MapsActivityCurrentPlace" />
    
    
  2. No método onCreate() da sua atividade, defina o arquivo de layout como a visualização de conteúdo:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
    }
    
  3. Implemente a interface OnMapReadyCallback e modifique o método onMapReady() para configurar o mapa quando o objeto GoogleMap estiver disponível:

    public void onMapReady(GoogleMap map) {
        mMap = map;
    
        // Do other setup activities here too, as described elsewhere in this tutorial.
    
        // Turn on the My Location layer and the related control on the map.
        updateLocationUI();
    
        // Get the current location of the device and set the position of the map.
        getDeviceLocation();
    }
    
  4. No método onCreate() da sua atividade, gere um identificador para o fragmento de mapa chamando FragmentManager.findFragmentById(). Em seguida, use getMapAsync() para se registrar no callback do mapa:

    SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
            .findFragmentById(R.id.map);
    mapFragment.getMapAsync(this);
    
  5. Escreva um método updateLocationUI() para definir os controles de local no mapa. Se o usuário tiver concedido permissão de localização, ative a camada "My Location" e o controle relacionado. Caso contrário, desative ambos e defina o local atual como "null":

    private void updateLocationUI() {
        if (mMap == null) {
            return;
        }
        try {
            if (mLocationPermissionGranted) {
                mMap.setMyLocationEnabled(true);
                mMap.getUiSettings().setMyLocationButtonEnabled(true);
            } else {
                mMap.setMyLocationEnabled(false);
                mMap.getUiSettings().setMyLocationButtonEnabled(false);
                mLastKnownLocation = null;
                getLocationPermission();
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }
    

Acessar o local do dispositivo Android e posicionar o mapa

Use o provedor de localização combinada para encontrar o último local conhecido do dispositivo e posicione o mapa nesse lugar. No tutorial, informamos o código que você precisa utilizar. Para mais detalhes sobre o local do dispositivo, consulte o guia sobre o provedor de localização combinada nas APIs Location do Google Play Services.

private void getDeviceLocation() {
    /*
     * Get the best and most recent location of the device, which may be null in rare
     * cases when a location is not available.
     */
    try {
        if (mLocationPermissionGranted) {
            Task locationResult = mFusedLocationProviderClient.getLastLocation();
            locationResult.addOnCompleteListener(this, new OnCompleteListener() {
                @Override
                public void onComplete(@NonNull Task task) {
                    if (task.isSuccessful()) {
                        // Set the map's camera position to the current location of the device.
                        mLastKnownLocation = task.getResult();
                        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
                                new LatLng(mLastKnownLocation.getLatitude(),
                                        mLastKnownLocation.getLongitude()), DEFAULT_ZOOM));
                    } else {
                        Log.d(TAG, "Current location is null. Using defaults.");
                        Log.e(TAG, "Exception: %s", task.getException());
                        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mDefaultLocation, DEFAULT_ZOOM));
                        mMap.getUiSettings().setMyLocationButtonEnabled(false);
                    }
                }
            });
        }
    } catch(SecurityException e)  {
        Log.e("Exception: %s", e.getMessage());
    }
}

Visualizar o lugar atual

Use o Places SDK for Android para ver uma lista de possíveis lugares atuais do dispositivo, que, nesse contexto, é uma empresa ou outro ponto de interesse.

Neste tutorial, o lugar atual é consultado quando o usuário clica no botão Get Place. Em seguida, é exibida uma lista de possíveis lugares para escolher, e um marcador é adicionado ao mapa, no local correspondente. No tutorial, fornecemos o código necessário para interagir com o Places SDK for Android. Se você quiser mais detalhes, consulte o guia sobre como visualizar o lugar atual.

  1. Crie um arquivo de layout (current_place_menu.xml) para o menu "opções" e modifique o método onCreateOptionsMenu() de forma a configurar esse menu. Veja o exemplo de app complementar para o código.
  2. Modifique o método onOptionsItemSelected() para acessar o lugar atual quando o usuário clicar na opção Get Place:
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.option_get_place) {
            showCurrentPlace();
        }
        return true;
    }
    
  3. Crie um método showCurrentPlace() para ver uma lista de possíveis lugares atuais do dispositivo:

    private void showCurrentPlace() {
        if (mMap == null) {
            return;
        }
    
        if (mLocationPermissionGranted) {
            // Use fields to define the data types to return.
            List<Place.Field> placeFields = Arrays.asList(Place.Field.NAME, Place.Field.ADDRESS,
                    Place.Field.LAT_LNG);
    
            // Use the builder to create a FindCurrentPlaceRequest.
            FindCurrentPlaceRequest request =
                    FindCurrentPlaceRequest.newInstance(placeFields);
    
            // Get the likely places - that is, the businesses and other points of interest that
            // are the best match for the device's current location.
            @SuppressWarnings("MissingPermission") final
            Task<FindCurrentPlaceResponse> placeResult =
                    mPlacesClient.findCurrentPlace(request);
            placeResult.addOnCompleteListener (new OnCompleteListener<FindCurrentPlaceResponse>() {
                @Override
                public void onComplete(@NonNull Task<FindCurrentPlaceResponse> task) {
                    if (task.isSuccessful() && task.getResult() != null) {
                        FindCurrentPlaceResponse likelyPlaces = task.getResult();
    
                        // Set the count, handling cases where less than 5 entries are returned.
                        int count;
                        if (likelyPlaces.getPlaceLikelihoods().size() < M_MAX_ENTRIES) {
                            count = likelyPlaces.getPlaceLikelihoods().size();
                        } else {
                            count = M_MAX_ENTRIES;
                        }
    
                        int i = 0;
                        mLikelyPlaceNames = new String[count];
                        mLikelyPlaceAddresses = new String[count];
                        mLikelyPlaceAttributions = new List[count];
                        mLikelyPlaceLatLngs = new LatLng[count];
    
                        for (PlaceLikelihood placeLikelihood : likelyPlaces.getPlaceLikelihoods()) {
                            // Build a list of likely places to show the user.
                            mLikelyPlaceNames[i] = placeLikelihood.getPlace().getName();
                            mLikelyPlaceAddresses[i] = placeLikelihood.getPlace().getAddress();
                            mLikelyPlaceAttributions[i] = placeLikelihood.getPlace()
                                    .getAttributions();
                            mLikelyPlaceLatLngs[i] = placeLikelihood.getPlace().getLatLng();
    
                            i++;
                            if (i > (count - 1)) {
                                break;
                            }
                        }
    
                        // Show a dialog offering the user the list of likely places, and add a
                        // marker at the selected place.
                        MapsActivityCurrentPlace.this.openPlacesDialog();
                    }
                    else {
                        Log.e(TAG, "Exception: %s", task.getException());
                    }
                }
            });
        } else {
            // The user has not granted permission.
            Log.i(TAG, "The user did not grant location permission.");
    
            // Add a default marker, because the user hasn't selected a place.
            mMap.addMarker(new MarkerOptions()
                    .title(getString(R.string.default_info_title))
                    .position(mDefaultLocation)
                    .snippet(getString(R.string.default_info_snippet)));
    
            // Prompt the user for permission.
            getLocationPermission();
        }
    }
    
  4. Crie um método openPlacesDialog() para exibir um formulário que permita ao usuário escolher um lugar em uma lista de opções. Adicione ao mapa um marcador para o lugar selecionado. O conteúdo do marcador inclui o nome e o endereço do local e todas as atribuições que a API fornece:

    private void openPlacesDialog() {
        // Ask the user to choose the place where they are now.
        DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // The "which" argument contains the position of the selected item.
                LatLng markerLatLng = mLikelyPlaceLatLngs[which];
                String markerSnippet = mLikelyPlaceAddresses[which];
                if (mLikelyPlaceAttributions[which] != null) {
                    markerSnippet = markerSnippet + "\n" + mLikelyPlaceAttributions[which];
                }
    
                // Add a marker for the selected place, with an info window
                // showing information about that place.
                mMap.addMarker(new MarkerOptions()
                        .title(mLikelyPlaceNames[which])
                        .position(markerLatLng)
                        .snippet(markerSnippet));
    
                // Position the map's camera at the location of the marker.
                mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(markerLatLng,
                        DEFAULT_ZOOM));
            }
        };
    
        // Display the dialog.
        AlertDialog dialog = new AlertDialog.Builder(this)
                .setTitle(R.string.pick_place)
                .setItems(mLikelyPlaceNames, listener)
                .show();
    }
    
  5. Crie um layout personalizado para o conteúdo da janela de informações. Isso permite a exibição de várias linhas de texto nessa janela. Primeiro, adicione um arquivo de layout XML, custom_info_contents.xml, contendo uma visualização de texto para o título da janela e outra para o snippet (isto é, o conteúdo de texto da janela):

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layoutDirection="locale"
        android:orientation="vertical">
        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textColor="#ff000000"
            android:textStyle="bold" />
    
        <TextView
            android:id="@+id/snippet"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#ff7f7f7f" />
    </LinearLayout>
    
    
  6. Implemente a interface InfoWindowAdapter para preencher o layout e carregar o conteúdo da janela de informações:

    @Override
    public void onMapReady(GoogleMap map) {
        // Do other setup activities here too, as described elsewhere in this tutorial.
        mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {
    
        @Override
        // Return null here, so that getInfoContents() is called next.
        public View getInfoWindow(Marker arg0) {
            return null;
        }
    
        @Override
        public View getInfoContents(Marker marker) {
            // Inflate the layouts for the info window, title and snippet.
            View infoWindow = getLayoutInflater().inflate(R.layout.custom_info_contents, null);
    
            TextView title = ((TextView) infoWindow.findViewById(R.id.title));
            title.setText(marker.getTitle());
    
            TextView snippet = ((TextView) infoWindow.findViewById(R.id.snippet));
            snippet.setText(marker.getSnippet());
    
            return infoWindow;
          }
        });
    }
    

Salvar o estado do mapa

Salve a posição da câmera do mapa e a localização do aparelho. Quando o usuário gira o dispositivo Android ou muda alguma configuração, o framework exclui a atividade no Google Maps e cria uma nova. Para aprimorar a experiência do usuário, armazene o estado de aplicativos relevantes e restaure-o quando necessário.

Neste tutorial, fornecemos todo o código necessário para salvar o estado do mapa. Se você quiser mais detalhes, consulte o guia do pacote savedInstanceState.

  1. Na atividade no Google Maps, configure chaves-valor para armazenar o estado da atividade:

    private static final String KEY_CAMERA_POSITION = "camera_position";
    private static final String KEY_LOCATION = "location";
    
  2. Implemente o callback onSaveInstanceState() para salvar o estado quando a atividade for pausada:

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if (mMap != null) {
            outState.putParcelable(KEY_CAMERA_POSITION, mMap.getCameraPosition());
            outState.putParcelable(KEY_LOCATION, mLastKnownLocation);
            super.onSaveInstanceState(outState);
        }
    }
    
  3. No método onCreate() da sua atividade, recupere a localização do dispositivo e a posição da câmera do mapa, caso tenham sido salvas anteriormente:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            mCurrentLocation = savedInstanceState.getParcelable(KEY_LOCATION);
            mCameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION);
        }
        ...
    }