Я — Денис, Middle Android-разработчик в «Black Bricks». Недавно в нашем KMP проекте возникла необходимость добавить рекламный баннер с GIF. В этой статье я расскажу, с какими трудностями мы столкнулись и как удалось реализовать этот функционал.

Исходно, стандартных решений для корректного воспроизведения GIF в Jetpack Compose мы не нашли. Основная сложность заключалась в том, что решение должно было работать одинаково стабильно как на Windows, так и на macOS. Сперва остановились на этом решении. Но уже на первых тестах стало понятно, что GIF больше 4 мегабайт эта реализация не тянет. Загрузка ЦП была под 80%.

Ещё немного пошерстив интернет, наткнулись на реализацию с загрузкой GIF из сети. Нам в целом было не принципиально откуда грузить. Но из-за специфики расположения баннера, и требований отобразить его как можно быстрее, хотелось всё же захардкодить его в сам проект.

Мы немного модифицировали решение для проигрывания из локальных ресурсов. Но так как в реализации используется библиотека javax.imageio.ImageIO, то нормально она под Windows не завелась. Долго разбираться не стали, тем более этот вариант немного лагал.

По итогу пришли к такому:

@Composable
fun GifImage() {
    val codec = remember {
        val path = "desktopApp/src/main/resources/images/sample.gif"
        val file = File(path)
        val bytes = file.readBytes()
        Codec.makeFromData(Data.makeFromBytes(bytes))
    }

    AnimatedGif(
        modifier = Modifier
            .fillMaxHeight()
            .aspectRatio(ratio = 1.5f, matchHeightConstraintsFirst = true),
        codec = codec,
    )
}

@Composable
fun AnimatedGif(codec: Codec, modifier: Modifier) {
    val transition = rememberInfiniteTransition()
    val frameIndex by transition.animateValue(
        initialValue = 0,
        targetValue = codec.frameCount - 1,
        typeConverter = Int.VectorConverter,
        animationSpec = infiniteRepeatable(
            animation = keyframes {
                durationMillis = 0
                for ((index, frame) in codec.framesInfo.withIndex()) {
                    index at durationMillis
                    durationMillis += frame.duration
                }
            }
        )
    )

    val bitmap = remember { Bitmap().apply { allocPixels(codec.imageInfo) } }
    Canvas(modifier) {
        codec.readPixels(bitmap, frameIndex)
        val imageBitmap = bitmap.asComposeImageBitmap()
        drawImage(
            image = imageBitmap,
            dstSize = IntSize(size.width.toInt(), size.height.toInt())
        )
    }
}

Эта реализация неплохо работает с GIF до 12 мегабайт, и без лагов при скролле списков.

Спасибо за чтение!

Денис Попков

Middle Android разработчик в «Black Bricks»

Если вы нашли неточности/ошибки в статье или просто хотите дополнить её своим мнением — то прошу в комментарии! Или можете написать мне в Telegram.

Комментарии (0)