Перейти к содержанию

Изображения

Меткам, кластерам и заливкам полигонов нужно изображение, и MapKit принимает его как ImageProvider. В общем коде ImageProvider — непрозрачный интерфейс: в commonMain нет ни Bitmap, ни UIImage, поэтому провайдер собирается на платформе или одним из ресурсных модулей.

Откуда изображение Что использовать
Ресурсы, ассеты, файлы, Bitmap на Android фабрики androidMain ниже
UIImage ImageProvider.fromUIImage
Ресурсы Compose Multiplatform imageProvider(Res.drawable.x)
Composable-содержимое imageProvider { }
moko-resources MOKOImageLoader

Из платформенного кода

Собирайте провайдеры там, где доступны платформенные типы, и передавайте их в общий код.

val fromDrawable = ImageProvider.fromResource(context, R.drawable.ic_cluster)
val fromAsset = ImageProvider.fromAsset(context, "cluster.png")
val fromFile = ImageProvider.fromFile("/path/to/cluster.png")
val fromBitmap = ImageProvider.fromBitmap(bitmap)

val alsoFromBitmap = bitmap.toImageProvider()

У каждой фабрики есть перегрузка с параметрами кеширования MapKit. fromBitmap требует ещё и id, потому что у растра нет естественного идентификатора:

val cached = ImageProvider.fromResource(
    context = context,
    resourceId = R.drawable.ic_cluster,
    isCacheable = true,
)

val cachedBitmap = ImageProvider.fromBitmap(
    bitmap = bitmap,
    isCacheable = true,
    id = "cluster-24",
)

Кеширование стоит включать

MapKit кеширует изображение по идентификатору и переиспользует декодированную копию для всех меток, которые им пользуются — на сотнях маркеров разница заметна.

val provider = ImageProvider.fromUIImage(uiImage)

iOS MapKit принимает обычный UIImage, поэтому здесь нет ни идентификатора, ни параметра кеширования — они остались только в androidMain.

Передача в общий код:

class MapIcons(
    val pin: ImageProvider,
    val cluster: ImageProvider,
)

fun setupMap(map: Map, icons: MapIcons) {
    map.mapObjects.addPlacemark().apply {
        geometry = Point(55.751225, 37.629540)
        setIcon(icons.pin)
    }
}
fun mapIcons(context: Context): MapIcons {
    return MapIcons(
        pin = ImageProvider.fromResource(context, R.drawable.pin),
        cluster = ImageProvider.fromResource(context, R.drawable.cluster),
    )
}
fun mapIcons(): MapIcons {
    return MapIcons(
        pin = ImageProvider.fromUIImage(UIImage.imageNamed("pin")!!),
        cluster = ImageProvider.fromUIImage(UIImage.imageNamed("cluster")!!),
    )
}

Анимированные изображения

AnimatedImageProvider питает анимированные иконки меток и анимированные заливки полигонов. В отличие от ImageProvider, его можно собрать целиком из общего кода:

val fromData = AnimatedImageProvider.fromByteArray(bytes)
val fromFile = AnimatedImageProvider.fromFile(path)

val built = AnimatedImageProvider.fromAnimatedImage(
    AnimatedImage(loopCount = 0).apply {
        addFrame(frameOne, 100.milliseconds)
        addFrame(frameTwo, 100.milliseconds)
    },
)

Кадры — это ImageProvider, так что они всё равно приходят с платформы. loopCount = 0 означает бесконечный цикл. На Android дополнительно есть AnimatedImageProvider.fromResource(context, id) и fromAsset(context, name).

moko-resources

Требуется yandex-mapkit-kmp-moko

MOKOImageLoader превращает ImageResource из moko в ImageProvider. Android-реализации нужен Context, iOS-реализации — нет, поэтому сам загрузчик создаётся на платформе и передаётся в общий код.

fun setupMap(map: Map, imageLoader: MOKOImageLoader) {
    val cluster = imageLoader.fromResource(MR.images.cluster)
    val pin = imageLoader.fromResource(MR.images.pin, isCacheable = true)
}
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setupMap(map, AndroidMOKOImageLoader(this))
    }
}
fun setupMap(map: Map) {
    setupMap(map, IOSMOKOImageLoader())
}

В composable-контексте yandex-mapkit-kmp-moko-compose убирает платформенный шаг целиком — см. Изображения в Compose.