Введение в новую эру картографических приложений Compose

С недавним выпуском Библиотеки Map Compose настало идеальное время, чтобы опробовать ее для проекта, который я создаю с помощью Jetpack Compose. В этом приложении я получаю список достопримечательностей (POI) с моего сервера в заданной области и отмечаю их на карте.

Я мог бы придерживаться GoogleMap на основе просмотра и обернуть его в AndroidView, чтобы вызвать его в мире Compose. Особенно, если вам понадобятся некоторые дополнительные функции, поскольку библиотека Compose все еще имеет некоторые ограничения (подробнее об этом позже).

Но я ожидаю, что некоторые из вас, как и я, будут рады открыть для себя эту новую библиотеку, созданную в Compose, для Compose.

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

Настройка вашей карты

Чтобы отрендерить карту в мобильном приложении, вам нужно пройти через эту утомительную часть, описанную в этой документации. Подводя итог, вы должны:

  • Создайте (или привяжите) свою учетную запись Google Cloud Billing к своему проекту. В 2018 году Google перешел на план с оплатой по мере использования, который требует, чтобы каждый проект предварительно настраивал ваши платежные данные. В этом руководстве мы будем использовать статическую карту с мобильным SDK, чтобы оставаться незамеченными. Но в зависимости от того, что вы позже планируете для своего приложения, необходимо учитывать потенциальные затраты. Подробнее здесь.
  • Включите Map SDK для своего проекта.
  • Создайте ключ API. Вы можете ограничить его, чтобы избежать злонамеренного использования, связав его с сертификатом SHA-1, который вы используете для подписи своего приложения.
  • Добавьте необходимые зависимости и свяжите свой ключ API с файлом AndroidManifest.

Самое интересное начинается после того, как вы выполнили вышеуказанные шаги.

Визуализация карты

Библиотека предоставляет GoogleMap Composable в качестве точки входа для визуализации вашей карты. Несколько необязательных аргументов позволяют настроить работу с картой.

Если вы не предоставите никаких аргументов, вы увидите карту мира с центром на экваторе и нулевом меридиане.

Если вы посмотрите на детали конструктора, GoogleMap принимает CameraPositionState с позицией по умолчанию на широте и долготе 0.

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

Карта управляется единственным источником правды: состоянием камеры CameraPositionState. Всякий раз, когда вы взаимодействуете с картой, состояние обновляется и происходит перекомпоновка.

В качестве примера я сосредоточу камеру на Бордо 🍷🥖🇫🇷. Мы запомним эти координаты с помощью функции rememberCameraPositionState и передадим их в GoogleMap Composable. Здесь я произвольно установил уровень масштабирования на 12, чтобы увидеть весь район Бордо.

Когда мы запускаем приложение, вот что мы получаем:

Это все, что вам нужно для отображения карты с помощью Map Compose! Впечатляет, нет? Теперь было бы здорово отобразить некоторые POI.

Отображение POI в пределах периметра

GoogleMap Composable принимает content лямбду в качестве последнего параметра, чтобы вы могли рисовать на карте. Для этого нам нужно взглянуть на Marker Composable. Он принимает MarkerState — оболочку для объекта LatLng.

По умолчанию Marker показывает классический красный пинпойнт. Вы можете настроить его с помощью других параметров.

Для своих целей я разработал API с маршрутом, который возвращает список объектов в определенном месте. Он принимает три параметра:

  • широта
  • долгота
  • радиус (в метрах)

Из CameraPositionState мы можем получить положение центра с камеры карты:

val centerLocation = cameraPositionState.position.target
val latitude = centerLocation.latitude
val longitude = centerLocation.longitude

Нам понадобится помощь в вычислении радиуса. Хитрость заключается в преобразовании уровня масштабирования, заданного картой — цифры, начинающейся от 0 (вся планета) до более чем 21, — в более удобный для человека радиус в метрах. Если вы хотите узнать больше о том, как Google отображает карту, вам будет очень интересна эта статья.

Google предоставляет служебную библиотеку карт, которая вычисляет для нас это расстояние, беря один угол карты (например, верхний левый) с центральным положением.

val topLeftLocation = cameraPositionState.projection?.visibleRegion?.farLeft
val radius = SphericalUtil.computeDistanceBetween(topLeftLocation, centerLocation)

Ваша карта должна быть готова для получения вышеуказанных значений (иначе проекция будет null). Хорошим местом для их запроса будет лямбда onMapLoaded. Здесь я попрошу свою ViewModel получить расположение POI в пределах вычисленного радиуса.

Наконец, я перебираю координаты маркера, чтобы сгенерировать все мои Marker Composables.

Чтобы имитировать это, вы можете создать набор координат и передать его вашему GoogleMap Composable так же, как вы получаете их из API.

Вот что вы увидите с координатами, которые дает мой сервер:

Но это только снимок некоторых POI из того, что видит камера. В конечном счете, вы захотите обновить эти POI при взаимодействии с вашей картой.

Взаимодействие с картой

Карта позволяет вам контролировать, что отображать, с помощью таких жестов, как панорамирование, масштабирование или даже вращение. Эти взаимодействия обязательно изменят то, что видит камера. Следовательно, ваши POI должны измениться соответствующим образом.

С Map Compose вы снова будете полагаться на свой единственный источник правды: CameraPositionState. У него есть свойство isMoving, которое изменится на true, как только карта переместится. И возвращается к false, когда камера простаивает.

У вас может возникнуть соблазн просто обернуть метод выборки внутри условия if, например:

Хотя это сработает, вы создадите избыточные запросы к своему серверу!

Это связано с характером работы рекомпозиции. Например, если вы перемещаете карту, положение центра камеры изменится. По мере движения карты вы будете обновлять свои POI с новым наблюдаемым центральным положением. Выбранные POI, вероятно, будут отличаться, поэтому ваше состояние изменится, что приведет к перекомпоновке.

Это означает, что вы снова нажмете условие и обновите свои POI, пока ваше состояние не перестанет меняться — это может привести к огромному количеству запросов!

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

Я сделал короткое видео, чтобы проиллюстрировать, как ведет себя обновление. Даже когда я быстро бросаю карту — карта размывается до тех пор, пока анимация не останавливается — POI отображаются, когда карта простаивает.

И что интересно с этим подходом Compose, так это производительность! Из-за системы перекомпоновки вы можете передать список POI, и на карте будут отображаться только эти маркеры. Не нужно заботиться о жизненном цикле или следить за тем, чтобы вы правильно удалили предыдущие маркеры, которые камера больше не отображает. Не говоря уже о том, что код легко читается и требует всего несколько строк кода.

Если вы все еще здесь, поздравляю! Мы рассмотрели некоторые основы использования функции составления карты и отображения POI при взаимодействии с картой.

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

Ловушки и ограничения

Есть вещи, которые вы, возможно, ожидаете иметь, но еще не готовы с Map Compose. Так обстоит дело с кластеризацией.

Этот механизм собирает набор POI в пределах одной области. Без него вы можете получить карту, забитую маркерами!

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

Вы можете достичь кластеризации благодаря ClusterManager из библиотеки util, упомянутой выше. Тем не менее, этому менеджеру нужен GoogleMap из библиотеки View — это может сбивать с толку, поскольку имя совпадает, но это не GoogleMap из библиотеки Compose.

Итак, когда я пишу эти строки, библиотека Map Compose еще не поддерживает кластеризацию. Следить за открытым выпуском можно здесь.

А пока у вас остается пара вариантов:

  1. Продолжайте использовать прежний API карт, пока не будет поддерживаться кластеризация.
  2. Ограничьте уровень масштабирования до разумного значения. Это может предотвратить рендеринг бесчисленных POI, а также предотвратить отставание карты. Но это не заменит хорошую кластеризацию, поскольку плотность ваших POI может варьироваться от одной области к другой. Ваш пользовательский опыт может ухудшиться в зависимости от данных, которые вы хотите отобразить.

Имея это в виду, вы готовы начать использовать библиотеку Map Compose. Я уверен, что это изменит правила игры для картографического приложения, как только оно догонит недостающие функции.

А пока следите за их репозиторием GitHub для будущих выпусков. Получайте удовольствие от составления карты!