Эффекты глитча (глюка, сбоя) — неотъемлемая часть научно-фантастических фильмов и киберпанк-игр. В этой статье мы увидим, как легко реализовать этот эффект в Jetpack Compose.
Это гораздо проще, чем кажется. К тому же, он открывает множество возможностей для творческих интерпретаций.
Графический слой
Поскольку мы будем рисовать несколько копий нашего Composable, нам нужно найти способ сделать это недорого и производительно.
Для этого мы можем использовать graphicsLayer.
val graphicsLayer = rememberGraphicsLayer()
После его создания мы можем «записать» наш контент в модификатор drawWithContent.
modifier = Modifier
.drawWithContent {
graphicsLayer.record { this@drawWithContent.drawContent() }
drawLayer(graphicsLayer)
}
Эти несколько мощных строк кода рисуют контент во внеэкранный буфер, который затем нужно отобразить вручную с помощью drawLayer. Преимущество такого подхода в том, что мы можем вызывать drawLayer несколько раз — с гораздо меньшими затратами производительности, чем при рендеринге нескольких composable-элементов.
Кроме того, у нас есть все наши стандартные функции холста для управления контентом.
Разрезание
Теперь давайте разрежем наше представление на несколько горизонтальных фрагментов. Позже мы применим случайные перемещения и масштабирование к каждому фрагменту.
graphicsLayer.record { this@drawWithContent.drawContent() }
for (i in 0 until slices) {
clipRect(
top = (i / slices.toFloat()) * size.height,
bottom = (((i + 1) / slices.toFloat()) * size.height) + 1f,
) {
drawLayer(graphicsLayer)
}
}
Здесь мы создаём «срезы», вызывая drawLayer несколько раз, но с разным клиппингом. Позиция и размер каждого фрагмента вычисляются в зависимости от количества срезов.
Я также добавил 1 px к высоте, чтобы избежать странных зазоров между ними.
Добавляем хаос
Здесь вы можете дать волю всевозможным случайным мутациям. В каждом срезе мы добавим случайное смещение (ось X), масштаб (ось X) и случайное наложение цвета.
Но, конечно, вы можете экспериментировать с множеством других вариантов.
translate(
left = if (Random.nextInt(5) < step)
Random.nextInt(-20..20).toFloat() * intensity
else
0f
) {
// ...
}
Мы применяем эти мутации, обёртывая clipRect другими функциями. Сначала применим смещение вдоль оси X с помощью translate(). Это случайным образом применит смещение в заданном диапазоне.
scale(
scaleY = 1f,
scaleX = if (Random.nextInt(10) < step)
1f + (1f * Random.nextFloat() * intensity)
else
1f
) {
// ...
}
Далее у нас идет scale(), который также масштабирует фрагмент по оси x.
drawLayer(graphicsLayer)
if (Random.nextInt(5, 30) < step) {
drawRect(
color = glitchColors.random(),
blendMode = BlendMode.SrcAtop
)
}
И наконец, мы можем просто случайным образом нарисовать прямоугольник случайным цветом.
Тайминг и плавность
Подождите-ка! Вы, возможно, спрашиваете себя, что это за step и intensity я использовал. Они нужны для тайминга анимации и придают ей более естественный вид, чем просто чистый хаос.
var step by remember { mutableStateOf(0) }
LaunchedEffect(key) {
Animatable(10f)
.animateTo(
targetValue = 0f,
animationSpec = tween(
durationMillis = 500,
easing = LinearEasing,
)
) {
step = this.value.roundToInt()
}
}
Переменная step — это целое число, которое анимируется от 10 до 0. Мы используем для этого целое число вместо числа с плавающей точкой, чтобы обеспечить дискретные изменения в нашем эффекте глитча. Всякий раз, когда меняется шаг, в нашем эффекте глитча будут происходить новые мутации.
Но мы также хотим, чтобы наш эффект начинался с хаоса, а затем постепенно затухал по мере его развития. Именно поэтому мы используем step, чтобы определить, когда следует применять мутацию.
left = if (Random.nextInt(5) < step)
Random.nextInt(-20..20).toFloat() * intensity
else
0f
Например, по мере уменьшения шага до 0 количество случаев применения данной мутации будет уменьшаться.
С помощью шага мы также можем получить интенсивность, которая должна применяться к каждой мутации.
val intensity = step / 10f
Применив эти две переменные, мы можем увидеть разницу, поскольку анимация замедляется к концу.
Итак, теперь мы знаем, как создать эффект глюка. Полный код Modifier вы можете найти тут.

