Я — Денис, 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.