| Предыдущая лекция | Следующая лекция | |
|---|---|---|
| Проект "каршеринг" Часть 2. Профиль пользователя. | Содержание | Wear OS |
Этот вариант написан для Яндекс.карт
Описание API каршеринга доступно по ссылке: https://swagger.kolei.ru/?url=https://carsharing.kolei.ru/swagger/api.yml
Переходим по ссылке (если у вас нет аккаунта yandex, то нужно сделать)
Нажмите "Подключить API" и выберите пакет "MapKit - мобильный SDK".
Заполните форму
В итоге у вас будет ключ
Создайте проект, откройте build.gradle проекта, В секции buildscript/repositories добавьте репозитории Maven Central и Google Maven (этот блок должен быть ДО секции plugins):
buildscript {
repositories {
mavenCentral()
maven {
url "https://maven.google.com/"
}
}
}
Откройте файл build.gradle приложения (модуля). В секции dependencies добавьте зависимость:
implementation 'com.yandex.android:maps.mobile:4.2.2-lite'
Добавьте карту на Activity:
<com.yandex.mapkit.mapview.MapView
android:id="@+id/mapview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Установите ваш API-ключ в методе onCreate() вашего наследника Application (судя по названию класса это должен быть наш класс MyApp, но я устанавливаю ключ в активности с картой):
Координаты текущей позиции я в примере задал фиксированные, вы в рамках практики используете определение координат по геолокации.
private lateinit var mapView: MapView
val yotcLat = 56.639478
val yotcLon = 47.892127
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MapKitFactory.setApiKey( ТУТ ВАШ КЛЮЧ )
Инициализируйте карту в методе onCreate() нужного Activity:
Внимание!!! Инициализацию нужно делать до вызова *setContentView*
MapKitFactory.setApiKey(apiKey)
MapKitFactory.initialize(this)
setContentView(R.layout.activity_main)
// получение указателя на визуальный элемент "карта"
mapView = findViewById(R.id.mapview)
// перемещение камеры в нужную точку
mapView.map.move(
CameraPosition(
Point(yotcLat, yotcLon),
18.0f, // зум
0.0f,
0.0f
),
Animation(Animation.Type.SMOOTH, 0f),
null
)
Передайте события onStart и onStop в MapKitFactory и MapView. Иначе MapKit не сможет отобразить карту и остановить обработку карты, когда Activity с картой становится невидимым для пользователя:
override fun onStart() {
super.onStart()
MapKitFactory.getInstance().onStart()
mapView.onStart()
}
override fun onStop() {
mapView.onStop()
MapKitFactory.getInstance().onStop()
super.onStop()
}
На этом этапе приложение уже должно нормально запускаться и показывать карту.
У яндекс карт, насколько я понял, нет стандарного значка для маркера текущей локации, все маркеры делаются одинаково из картинок (есть варианты и из фигур, но их рассматривать не будем). Добавьте в ресурсы приложения картинку для маркера текущей локации (в каталог res/drawable) и создайте маркер (в конструкторе, после камеры):
val imageProvider: ImageProvider = ImageProvider
.fromResource(
this,
R.drawable.location_marker)
mapView
.map
.mapObjects
.addPlacemark(
Point(
yotcLat,
yotcLon),
imageProvider)
В принципе само добавление маркеров аналогичное, но нам ещё нужно реализовать клик на маркере автомобиля, чтобы отображалась подробная информация об автомобиле (фотография, модель, остаток хода, тариф, кнопка перехода к бронированию).
Обработчик клика по маркеру у яндекса реализован со "слабой связанностью", т.е. сборщик мусора его уничтожит при ближайшей возможности. Чтобы этого не происходило, маркеры нужно хранить в отдельном списке.
В свойства класса добавьте массив для созданных маркеров и указатель на обработчик клика (тапа) по маркеру (пока пустой):
private var carList = ArrayList<PlacemarkMapObject>()
private var markerTapListener: MapObjectTapListener? = null
Добавьте методы для задания обработчика и для добавления маркера:
private fun setTapListener(listener: MapObjectTapListener) {
markerTapListener = listener
}
/*
Это обёртка над addPlacemark
в которой к маркеру цепляется обработчик событий
и полезная нагрузка
*/
private fun addMarker(
latitude: Double,
longitude: Double,
@DrawableRes imageRes: Int,
userData: Any? = null
): PlacemarkMapObject
{
val marker = mapView.map.mapObjects
.addPlacemark(
Point(latitude, longitude),
ImageProvider
.fromResource(
this,
imageRes)
)
marker.userData = userData
markerTapListener?.let{
marker
.addTapListener(it) }
return marker
}
В качестве "полезной нагрузки" будем хранить информацию об автомобиле. То есть у нас будет не список экземпляров класса Car, а список маркеров, в полезной нагрузке которых и будут хранится экземпляры Car
То есть формирование списка маркеров будет происходить при получении ответа сервера на запрос списка автомобилей примерно так:
for(...) {
// создайте экземпляр информации об автомобиле
val car = Car(...)
markerList.add(
addMarker(
car.lat,
car.lon,
R.drawable.car,
car
)
)
}
В яндекс картах я не нашёл аналога InformationWindow - окна, появляющегося при клике на маркер у гугла. Реализуем показ обычными визуальными элементами:
Добавьте в разметку окна LinearLayout с id = detailInfo и с необходимым содержимым. Задайте ему атрибут visibility="gone" (не показывать и не занимать место)
Задайте обработчик клика.
setTapListener { mapObject, point ->
detailInfo.visibility = View.VISIBLE
// mapObject.userData содержит экземпляр Car, который вы задали при создании маркера
val car = mapObject.userData as Car
// тут в визуальные элементы вашего detailInfo внесите информацию об автомобиле (фото, модель...)
...
true
}
Должно получиться что-то такое (я корневым элементом сделал CardView, чтобы углы были скруглённые)
Библиотека Glide предназначена для асинхронной подгрузки изображений из сети, ресурсов или файловой системы, их кэширования и отображения. Библиотека имеет дополнительную функциональность в виде поддержки анимированных GIF-файлов и видео.
Кстати, Google высоко оценивает качество библиотеки и даже включила её в состав одного шаблона в Android Studio.
Добавить в зависимости:
implementation 'com.github.bumptech.glide:glide:4.14.2'
annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2'
При получении данных из АПИ сразу формировать полный URL картинки
var car = Car(
item.getString("model"),
"http://carsharing.kolei.ru/images/${item.getString("photo")}"
)
При необходимости загрузить изображение вызываем конструкцию
Glide.with(this)
.load(car.photoUrl)
.into(carPhotoImageView)
где
Для начала поменяйте атрибуты контейнера:
android:layout_marginBottom="-432dp"Используя способ анимации свойств объекта сделаем, чтобы наше окно с детальной информацией всплывало при клике на маркер и скрывалось при клике на карту:
Создайте в каталоге res каталог animator (New -> Android Resource Directory)
В этом каталоге создайте два файла анимации, для открытия и закрытия окна:
Моё окно высотой
432dpсо скруглёнными углами32dp. Чтобы его не было видно в неактивном состоянии в разметке задана нижняя граница-432dp(по размеру элемента)Правильнее было бы нижнюю границу и менять при анимации, но похоже класс Animator не понимает свойства
layout_
open_animator.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="300"
android:propertyName="translationY"
android:valueTo="-400dp"
android:valueType="floatType"
android:interpolator="@android:anim/decelerate_interpolator"/>
</set>
Обратите внимание, окно поднимается не на всю высоту, а за вычетом размера радиуса, чтобы нижнее скругление не было видно. Меняется свойство translationY
и close_animator.xml:
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="300"
android:propertyName="translationY"
android:valueTo="0dp"
android:valueType="floatType"
android:interpolator="@android:anim/decelerate_interpolator"/>
</set>
В классе окна включить анимацию открытия при тапе по маркеру и анимацию закрытия при тапе по карте или по кнопке "закрыть" в окне:
Меняем обработчик тапа для маркера:
setTapListener { mapObject, point ->
// тут предыдущий код с заполнением окна данными выбранного автомобиля
// запоминаем состояние окна в переменной класса
isOpen = true
// извлекаем нужный аниматор
val set = AnimatorInflater.loadAnimator(this, R.animator.open_animator) as AnimatorSet
// назначаем целевой визуальный элемент
set.setTarget(detailInfo)
// и запускаем анимацию
set.start()
true
}
Обработчика тапа по карте у нас ещё не было, создадим:
private val inputListener: InputListener = object : InputListener {
override fun onMapTap(
p0: com.yandex.mapkit.map.Map,
p1: Point)
{
closePopup()
}
override fun onMapLongTap(
p0: com.yandex.mapkit.map.Map,
p1: Point)
{
// обработка длинного тапа нам пока не нужна
}
}
// в методе onCreate цепляем обработчик к карте
mapView.map.addInputListener(inputListener)
Метод closePopup вынесен отдельно, т.к. в контексте объекта мы не видим экземпляр активности (this)
private fun closePopup(){
if(isOpen) {
isOpen = false
val set = AnimatorInflater.loadAnimator(
this,
R.animator.close_animator
) as AnimatorSet
set.setTarget(detailInfo)
set.start()
}
}
Для не сложных преобразований проще использовать программную анимацию (на примере открытия):
detailInfo
.animate()
.setDuration(300)
.translationY(-resources.displayMetrics.ydpi * 400f / 160)
.start()
Метод translationY оперирует пикселями, поэтому мы должны преобразовать реальное разрешение экрана к размерности dp (160ppi). Цифра 400 это высота моего окна.
![]() |
Пример анимации |
| Предыдущая лекция | Следующая лекция | |
|---|---|---|
| Проект "каршеринг" Часть 2. Профиль пользователя. | Содержание | Wear OS |