Выбор текущего местоположения и просмотр подробных сведений на карте

В этом руководстве рассказывается, как определить текущее местоположение устройства Android и показать сведения об организации или другом объекте, который там находится. Следуйте руководству по созданию приложения для Android с помощью Maps SDK for Android, Places SDK for Android и поставщика геоданных из нескольких источников в Location API сервисов Google Play.

Как получить код

Клонируйте или скачайте репозиторий с примерами Google Maps Android API версии 2 с сайта GitHub.

Настройка проекта

Чтобы создать проект в Android Studio, выполните следующие действия:

  1. Скачайте и установите Android Studio.
  2. Добавьте пакет сервисов Google Play в Android Studio.
  3. Клонируйте или скачайте репозиторий с примерами Google Maps Android API версии 2 с сайта GitHub, если вы ещё не сделали этого.
  4. Импортируйте обучающий проект:

    • В Android Studio выберите Файл > Создать > Импортировать проект.
    • Перейдите в каталог, где вы сохранили репозиторий с примерами Google Maps Android API версии 2 после его скачивания.
    • Найдите проект CurrentPlaceDetailsOnMap, используя этот путь:
      PATH-TO-SAVED-REPO/android-samples/tutorials/CurrentPlaceDetailsOnMap
    • Выберите каталог проекта и нажмите ОК. Теперь Android Studio создаст ваш проект с использованием инструмента сборки Gradle.

Как получить ключ API и активировать нужные API

Для выполнения этого учебного проекта вам понадобится ключ Google API, который авторизован для работы с Maps SDK for Android и Places SDK for Android.

Нажмите кнопку ниже, чтобы получить ключ и активировать API.

Начать

Более подробную информацию можно найти в полном руководстве по получению ключа API.

Как добавить ключ API в свое приложение

  1. Отредактируйте файл gradle.properties в своем проекте.
  2. Подставьте ключ API в качестве значения для свойства GOOGLE_MAPS_API_KEY.

    GOOGLE_MAPS_API_KEY=PASTE-YOUR-API-KEY-HERE

    Во время сборки приложения Gradle копирует ключ API в манифест приложения Android. Этот файл build.gradle содержит код, который сопоставляет строку google_maps_key в манифесте со свойством Gradle GOOGLE_MAPS_API_KEY:

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

Как создать сборку и запустить приложение

  1. Подключите устройство Android к компьютеру. Выполните инструкции по активации параметров для разработчиков на устройстве Android и настройте ваше приложение и систему для обнаружения этого устройства. Для настройки виртуального устройства вы также можете использовать Менеджер виртуального устройства Android (AVD). Выбирая эмулятор, убедитесь, что вы используете образ, который содержит интерфейсы Google API. Подробную информацию можно найти в руководстве по началу работы.
  2. В Android Studio выберите пункт меню Запустить или нажмите на значок воспроизведения, чтобы запустить свое приложение. В открывшемся окне выберите устройство.

Android Studio запустит Gradle для сборки приложения, а затем отобразит результаты на устройстве или в эмуляторе. Вы должны увидеть карту с набором маркеров, в центре которой будет ваше актуальное местоположение, как показано на иллюстрации на этой странице.

Устранение неполадок

  • Если карта не отображается, проверьте, получен ли ключ API и добавлен ли он в приложение как описано выше. Проверьте журнал Android Monitor в Android Studio на наличие сообщений об ошибках, касающихся ключа API.
  • Если на карте отображается только один маркер, установленный на Сиднейском мосту (местоположение по умолчанию, указанное в приложении), проверьте, предоставили ли вы приложению доступ к данным о местоположении. Приложение запрашивает доступ в процессе запуска, используя шаблон, который приводится в руководстве с описанием разрешений Android. Обратите внимание, что разрешения можно также предоставить непосредственно на устройстве, выбрав Настройки > Приложения > название приложения > Разрешения > Местоположение. Подробную информацию о том, как указывать разрешения в коде, вы найдете ниже, в руководстве по запросу доступа к данным о местоположении.
  • Используйте средства отладки Android Studio чтобы просмотреть журналы и выполнить отладку приложения.

Понимание кода

В этой части руководства представлено объяснение наиболее важных компонентов приложения CurrentPlaceDetailsOnMap, помогающее понять принципы создания подобного приложения.

Как создать экземпляр клиента для Places API

Интерфейсы, которые являются основными компонентами для работы с Places SDK for Android:

  • Интерфейс GeoDataClient обеспечивает доступ к базе данных Google с информацией о местах и компаниях.
  • Интерфейс PlaceDetectionClient позволяет быстро получить доступ к информации о текущем местоположении устройства, а также обеспечивает возможность создавать отчеты о том, что устройство находится в определенном месте.

Интерфейс LocationServices – главная точка входа для сервисов определения местоположения на платформе Android.

Чтобы использовать API, создайте экземпляры GeoDataClient, PlaceDetectionClient и FusedLocationProviderClient в методе onCreate() своего фрагмента или объекта activity, как показано в примере ниже:

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);
}

Запрос доступа к данным о местоположении

Ваше приложение должно запрашивать доступ к данным о местоположении, чтобы определить, где находится устройство, и предоставить пользователю возможность нажимать кнопку Мое местоположение на карте.

Это руководство содержит код, который необходим для запроса доступа к точным данным о местоположении. Подробную информацию можно найти в руководстве по разрешениям Android.

  1. Добавьте следующее разрешение в качестве дочернего для элемента <manifest> в вашем манифесте 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. Запросите разрешения при запуске приложения, чтобы пользователь мог принять или отклонить запрос доступа к данным о местоположении. Следующий код позволяет проверить, предоставил ли пользователь доступ к данным о точном местоположении. Если пользователь не предоставил разрешение, код выполнит соответствующий запрос:

    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. Переопределите обратный вызов метода onRequestPermissionsResult(), чтобы обработать результат запроса доступа:

    @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();
    }
    

    Метод updateLocationUI() мы рассмотрим ниже.

Добавление карты

Отобразите карту, используя Maps SDK for Android.

  1. Добавьте элемент <fragment> в файл макета для объекта Activity, activity_maps.xml. Этот элемент указывает, что фрагмент SupportMapFragment должен выступать в роли контейнера для карты и предоставить доступ к объекту GoogleMap. В учебном проекте используется версия вспомогательной библиотеки Android для фрагмента карты, чтобы обеспечить обратную совместимость с более ранними версиями фреймворка 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. В методе onCreate() своего объекта activity установите файл макета как представление контента.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
    }
    
  3. Реализуйте интерфейс OnMapReadyCallback и переопределите метод onMapReady(), чтобы настроить карту, когда объект GoogleMap будет доступен:

    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. В методе onCreate() своего объекта activity получите дескриптор для фрагмента карты путем вызова метода FragmentManager.findFragmentById(). Затем используйте метод getMapAsync(), чтобы зарегистрировать обратный вызов карты:

    SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
            .findFragmentById(R.id.map);
    mapFragment.getMapAsync(this);
    
  5. Разместите в коде метод updateLocationUI(), чтобы добавить на карту элементы управления. Если пользователь предоставил разрешение на использование данных о местоположении, активируйте слой "Мое местоположение" и соответствующий элемент управления на карте. В противном случае отключите их и установите для текущего местоположения значение 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());
        }
    }
    

Получение информации о местоположении устройства Android и позиционирование карты

Обратитесь к поставщику геоданных из нескольких источников, чтобы определить последнее известное местоположение устройства. Затем используйте эти данные для позиционирования карты. В этом руководстве есть код, который вам понадобится. Дополнительную информацию о том, как определить местоположение устройства, можно найти в руководстве по работе с поставщиком геоданных из нескольких источников для Location API сервисов Google Play.

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());
    }
}

Получение данных об актуальном местоположении

Получить список мест, где с вероятностью может находиться устройство, можно с помощью Places SDK for Android. Место в данном случае – это компания или другой объект для посещения.

Выполните приведенные ниже инструкции, чтобы настроить показ места, когда пользователь нажимает кнопку Узнать место. Пользователь увидит список подходящих мест и сможет сделать выбор, а затем добавить на карту маркер, чтобы указать местоположение выбранного места. В этом руководстве вы найдете код для взаимодействия с Places SDK for Android. Дополнительную информацию можно найти в руководстве по получению информации о текущем месте.

  1. Создайте файл макета (current_place_menu.xml) для меню параметров и переопределите метод onCreateOptionsMenu(), чтобы настроить это меню. Ознакомьтесь с кодом в примере приложения.
  2. Переопределите метод onOptionsItemSelected(), чтобы получать актуальную информацию о месте, когда пользователь нажимает Узнать место:
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.option_get_place) {
            showCurrentPlace();
        }
        return true;
    }
    
  3. Создайте метод showCurrentPlace(), чтобы получить список мест, где может находиться устройство:

    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. Создайте метод openPlacesDialog(), чтобы показать форму, в которой пользователь может выбрать место из списка. Добавьте на карту маркер для выбранного места. Маркер должен содержать сведения о названии и адресе места, а также другие атрибуты, которые предоставляет API:

    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. Создайте собственный макет для контента информационного окна. Это позволяет показывать в окне несколько строк. Сначала добавьте файл макета XML (custom_info_contents.xml), который содержит текстовое представление для заголовка информационного окна, и еще одно текстовое представление для фрагмента (иными словами, текстовый контент информационного окна).

    <?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. Реализуйте интерфейс InfoWindowAdapter, чтобы развернуть макет и загрузить контент информационного окна:

    @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;
          }
        });
    }
    

Сохранение состояния карты

Сохраните информацию о положении камеры и местоположении устройства. Когда пользователь поворачивает устройство Android или изменяет параметры конфигурации, фреймворк Android останавливает и заново запускает объект activity для карты. Чтобы обеспечить удобство для пользователя, сохраните соответствующее состояние приложения и восстановите его при необходимости.

Это руководство содержит весь код, который необходим для сохранения состояния карты. Дополнительную информацию вы можете найти в руководстве по использованию пакета savedInstanceState.

  1. В операции, работающей с картой, настройте основные значения для сохранения состояния объекта activity:

    private static final String KEY_CAMERA_POSITION = "camera_position";
    private static final String KEY_LOCATION = "location";
    
  2. Реализуйте обратный вызов onSaveInstanceState(), чтобы сохранить состояние, когда операция будет приостановлена:

    @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. В методе onCreate() своей операции получите информацию о местоположении устройства и положении камеры карты, если это было ранее сохранено:

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