Объекты карты¶
API состояний¶
content у YandexMap() — это отдельная композиция, узлы которой являются объектами карты. Её
composable-функции помечены @YandexMapComposable, и composable-функции Compose UI туда поставить
нельзя.
| Composable | Что рисует |
|---|---|
Placemark |
точку с иконкой |
TitledPlacemark |
метку с текстовой подписью |
Polyline |
линию |
Polygon |
залитую область |
Circle |
окружность с радиусом в метрах |
Clustering |
метки, сгруппированные в кластеры |
MapObjectCollection |
вложенную группу |
MapObjectLayer |
группу на отдельном слое |
TileLayer, TrafficLayer |
слои карты |
MapListeners |
события карты |
Каждая composable-функция объекта принимает объект состояния, созданный через rememberXxxState().
Геометрия живёт в состоянии, а не в параметре, поэтому перемещение объекта — это запись в состояние,
которая не рекомпонует поддерево.
Метка¶
val placemarkGeometry = Point(55.751225, 37.629540)
@Composable
fun MapScreen() {
val cameraPositionState = rememberCameraPositionState { position = startPosition }
YandexMap(
cameraPositionState = cameraPositionState,
modifier = Modifier.fillMaxSize(),
) {
Placemark(
state = rememberPlacemarkState(placemarkGeometry),
icon = imageProvider(Res.drawable.pin_red),
iconStyle = IconStyle(anchor = PointF(0.5f, 1f), scale = 1.2f),
onTap = { point -> true },
visible = true,
draggable = false,
zIndex = 1f,
opacity = 1f,
userData = poi,
)
}
}
imageProvider(Res.drawable.pin_red) собирает иконку из ресурса Compose Multiplatform — см.
Изображения.
TitledPlacemark добавляет подпись:
TitledPlacemark(
state = rememberPlacemarkState(placemarkGeometry),
icon = imageProvider(Res.drawable.pin_red),
title = "Красная площадь",
titleStyle = TextStyle(size = 10f, placement = TextStyle.Placement.BOTTOM, offset = 4f),
)
Окружность¶
Circle(
state = rememberCircleState(Circle(center = point, radius = 500f)),
color = Color.Red.copy(alpha = 0.2f),
strokeColor = Color.Red,
strokeWidth = 2f,
geodesic = false,
onTap = { true },
)
Ломаная¶
Polyline(
state = rememberPolylineState(Polyline(points)),
strokeColor = Color.Green,
strokeWidth = 5f,
outlineColor = Color.DarkGray,
outlineWidth = 1f,
dashLength = 8f,
gapLength = 4f,
turnRadius = 10f,
)
Полигон¶
Polygon(
state = rememberPolygonState(
Polygon(outerRing = LinearRing(outerPoints), innerRings = listOf(LinearRing(hole))),
),
color = Color.Blue.copy(alpha = 0.2f),
strokeColor = Color.Blue,
strokeWidth = 2f,
geodesic = true,
pattern = imageProvider(Res.drawable.hatch),
patternScale = 1f,
)
Что добавляет состояние¶
Объект состояния — место для всего, что нельзя выразить параметром. Он связан с объектом карты, пока composable находится в композиции, поэтому такие вызовы делают в эффекте или обработчике события, а не в самой композиции.
Любой объект¶
mapObjectState.setVisible(
visible = false,
animation = Animation(Animation.Type.SMOOTH, 300.milliseconds),
)
mapObjectState.isValid
Метка¶
PlacemarkState достаёт объекты представления и функцию масштаба:
val placemarkState = rememberPlacemarkState(point)
LaunchedEffect(Unit) {
placemarkState.setScaleFunction(listOf(PointF(10f, 0.5f), PointF(16f, 1f)))
placemarkState.useCompositeIcon().setIcon("badge", badgeImage, IconStyle(zIndex = 1f))
placemarkState.text.text = "Красная площадь"
}
geometry, direction и isDragging — наблюдаемые свойства состояния:
val placemarkState = rememberPlacemarkState(point)
Placemark(state = placemarkState, icon = icon, draggable = true)
if (placemarkState.isDragging) {
Text("Dragging from ${placemarkState.geometry}")
}
Цвета сегментов ломаной¶
Ломаная красится через палитру. setPaletteColor(colorIndex, color) задаёт цвет под индексом, а
setStrokeColors(paletteIndices) раздаёт эти индексы сегментам — у ломаной из n точек n − 1
сегментов. По умолчанию все сегменты используют нулевой индекс палитры, который и задаёт параметр
strokeColor.
getStrokeColor(segmentIndex) возвращает индекс палитры сегмента, а не его цвет; разрешите его
через getPaletteColor(colorIndex).
polylineState.setPaletteColor(0, Color.Red)
polylineState.setPaletteColor(1, Color.Green)
polylineState.setPaletteColor(2, Color.Blue)
polylineState.setStrokeColors(listOf(0, 1, 2))
PolylineState также выделяет и прячет участки линии и добавляет стрелки:
polylineState.select(Color.Blue, subpolyline)
polylineState.hide(subpolyline)
polylineState.addArrow(
position = PolylinePosition(segmentIndex = 2, segmentPosition = 0.0),
length = 12f,
fillColor = Color.Black,
)
Заливка полигона¶
polygonState.setPattern(animatedHatch, scale = 1f)
polygonState.resetPattern()
Коллекции¶
collectionState.setPlacemarksScaleFunction(listOf(PointF(10f, 0.5f), PointF(16f, 1f)))
collectionState.traverse(visitor)
Кластеризация¶
Clustering собирает ClusterizedPlacemarkCollection и перекластеризует, когда меняются группы. У
каждой ClusterGroup своя иконка метки; иконка кластера — общая.
@Composable
fun MapScreen() {
val cameraPositionState = rememberCameraPositionState { position = startPosition }
val pinRed = imageProvider(Res.drawable.pin_red)
val clusterIcon = imageProvider(Res.drawable.cluster)
YandexMap(
cameraPositionState = cameraPositionState,
modifier = Modifier.fillMaxSize(),
) {
Clustering(
group = ClusterGroup(points = redPoints, icon = pinRed),
icon = clusterIcon,
config = ClusterizingConfig(clusterRadius = 60.0, minZoom = 15),
onItemTap = { item -> true },
onClusterTap = { cluster -> true },
)
}
}
Clustering(
groups = persistentListOf(
ClusterGroup(points = redPoints, icon = pinRed),
ClusterGroup(points = greenPoints, icon = pinGreen),
ClusterGroup(points = yellowPoints, icon = pinYellow),
),
icon = clusterIcon,
)
Clustering(
group = ClusterGroup(
placemarks = pois.map { ClusterItem(geometry = it.point, data = it) }.toPersistentList(),
icon = pinRed,
text = "POI",
),
icon = clusterIcon,
onItemTap = { item ->
val poi = item.data as? Poi ?: return@Clustering false
open(poi)
true
},
)
groups — это ImmutableList намеренно: так Compose может пропустить рекомпозицию, когда список не
менялся, а перекластеризация дорогая.
Иконки кластеров тоже могут быть composable-содержимым — см. Изображения.
API контроллера¶
С перегрузкой-контроллером композиции объектов карты нет: MapControllerEffect отдаёт MapWindow,
как только тот появится, а дальше вы работаете напрямую через API обёртки.
@Composable
fun MapScreen() {
val mapController = rememberYandexMapController()
val pinGreen = imageProvider(Res.drawable.pin_green)
MapControllerEffect(mapController) { mapWindow ->
mapWindow.map.mapObjects.addPlacemark().apply {
geometry = placemarkGeometry
setIcon(pinGreen)
}
}
YandexMap(
controller = mapController,
modifier = Modifier.fillMaxSize(),
)
}
MapControllerEffect(mapController) { mapWindow ->
mapWindow.map.mapObjects.addCircle(circleGeometry)
}
MapControllerEffect(mapController) { mapWindow ->
mapWindow.map.mapObjects.addPolyline(polylineGeometry)
}
MapControllerEffect(mapController) { mapWindow ->
mapWindow.map.mapObjects.addPolygon(polygonGeometry)
}
Кластеризация делается так же, как в обёртке, вместе со слушателями:
@Composable
fun MapScreen() {
val mapController = rememberYandexMapController()
val clusterIcon = imageProvider(Res.drawable.cluster)
val pinRed = imageProvider(Res.drawable.pin_red)
val clusterTapListener = remember { ClusterTapListener { true } }
val itemTapListener = remember { MapObjectTapListener { _, _ -> true } }
val clusterListener = remember(clusterIcon) {
ClusterListener { cluster ->
cluster.appearance.setIcon(clusterIcon)
cluster.appearance.setText("${cluster.size}")
cluster.addClusterTapListener(clusterTapListener.asWeakRef())
}
}
MapControllerEffect(mapController) { mapWindow ->
val collection = mapWindow.map.mapObjects
.addClusterizedPlacemarkCollection(clusterListener.asWeakRef())
points.forEach { point ->
collection.addPlacemark().apply {
geometry = point
setIcon(pinRed)
addTapListener(itemTapListener.asWeakRef())
}
}
collection.clusterPlacemarks(clusterRadius = 60.0, minZoom = 15)
}
YandexMap(
controller = mapController,
modifier = Modifier.fillMaxSize(),
)
}
Слушателей нужно remember
Подписки слабые. Слушатель, созданный по месту в композиции, будет собран на следующей
рекомпозиции и перестанет срабатывать — оберните его в remember или держите там, кому
принадлежит экран.