Connect with us

Разработка

Создание анимированных полос в Jetpack Compose

Это хорошо работает в загрузчиках или компонентах, где нужно отображать текущее состояние. Или можно использовать это для добавления текстур.

Опубликовано

/

     
     

Если мы хотим нарисовать и анимировать полоски в Jetpack Compose, мы можем использовать градиент с его параметрами для создания подобного варианта:

Управление цветовыми точками

Суть в том, чтобы расположить определённые цветовые точки градиента (color stop’ы) рядом друг с другом. Это создаёт резкие переходы вместо плавных градиентов.

На иллюстрации видно, как две средние точки всё ближе сходятся друг к другу, пока не формируют чёткую границу:

Чтобы создать эту жесткую границу в коде, мы просто устанавливаем две средние точки градиента в одной и той же точке, но с разными цветами:

Modifier.drawBehind {
    drawRect(
        brush = Brush.linearGradient(
            0f to Color.Black,
            .5f to Color.Black,
            .5f to Color.White,
            1f to Color.White,
        )
    )
}

Значения 0f для Color.Black и .5f для Color.Black задают чёрный цвет для первой половины. Затем, .5f для Color.White и 1f для Color.White задают белый цвет для второй половины. Резкий переход на .5f определяет край полосы.

В следующем разделе мы научимся автоматизировать определение цветовых точек каждой полосы.

Рисование полос

Сейчас у нас есть только две половины, но не повторяющиеся полосы. Чтобы создать полосы, нам нужно повторить эти цвета несколько раз по всей области:

Modifier.drawBehind {
    drawRect(
        brush = Brush.linearGradient(
            0f to Color.Black,
            .5f to Color.Black,
            .5f to Color.White,
            1f to Color.White,
            start = Offset(0f, 0f),
            end = Offset(20f, 0f),
            tileMode = TileMode.Repeated,
        )
    )
}

Это можно сделать, задав start и end смещения для управления углом и размером одной итерации. Затем мы устанавливаем tileMode в TileMode.Repeated, что позволяет разбить узор на на всю поверхность.

Анимируем полосы

Теперь, когда мы научились рисовать полоски, давайте научимся их анимировать. Для этого достаточно просто сместить начальную и конечную точки.

start = animatedOffset + Offset(0f, 0f),
end = animatedOffset + Offset(20f, 0f),

Таким образом, мы можем заставить полоски двигаться, анимируя animatedOffset так, как нам нужно.

Небольшая вспомогательная функция

До сих пор мы изучали основы создания и анимации полос с помощью градиентов. Но я обнаружил, что при построении более сложных узоров это может быть довольно сложно. Чтобы сделать создание таких дизайнов менее утомительным, давайте создадим вспомогательную функцию.

fun Brush.Companion.stripes(  
    vararg stripes: Pair<Color, Float>,  
    width: Float = 20f,  
    angle: Float = 45f,  
    phase: Float = 0f,  
): Brush {  
    val totalWeight = stripes.sumOf {  
        it.second.toDouble()  
    }.toFloat()  
  
    val colorStops = mutableListOf<Pair<Float, Color>>()  
    var currentPosition = 0f  
  
    stripes.forEach { (color, weight) ->  
        val proportion = weight / totalWeight  
        colorStops.add(currentPosition to color)  
        currentPosition += proportion  
        colorStops.add(currentPosition to color)  
    }  
  
    val angleInRadians = angle * (PI / 180)  
    val endX = (width * cos(angleInRadians)).toFloat()  
    val endY = (width * sin(angleInRadians)).toFloat()  
  
    val phaseOffsetX = endX * phase  
    val phaseOffsetY = endY * phase  
  
    return linearGradient(  
        colorStops = colorStops.toTypedArray(),  
        start = Offset(-phaseOffsetX, -phaseOffsetY),  
        end = Offset(endX - phaseOffsetX, endY - phaseOffsetY),  
        tileMode = TileMode.Repeated,  
    )  
}

Первое и самое важное препятствие, которое нужно преодолеть, — это границы цвета. Устанавливая их вручную можно сделать ошибку, да и в целом это не очень увлекательно. Всё просто, когда границ у цвета всего три — 0, 0.5 и 1. Но что, если нам нужно несколько цветов разного размера?

Вместо этого давайте просто передадим цвета в списке, сопоставив их с весом. Вес определяет пропорции. Если все веса равны, все полосы равны. Если вес одной полосы 2f, а другой — 1f, первая займёт 2/3 пространства.

// Equal stripes
Brush.stripes(
    Pink400 to 1f,
    Transparent to 1f,
)

// Pink twice as wide
Brush.stripes(
    Pink400 to 2f,
    Transparent to 1f,
)

// Multiple colors
Brush.stripes(
    Red to 1f,
    Blue to 2f,
    Green to 1f,
)

Далее, вместо того, чтобы работать со смещениями начала и конца, мы можем определить один повтор с шириной и углом. Параметр width задаёт размер одного полного повторения. Угол angle описывает поворот в градусах. — для горизонтали, 90° — для вертикали, 45° — для диагонали.

Наконец, у нас есть phase. Если вы знакомы с Path Effects, то знаете, как можно использовать phase для смещения начальной точки. Мы будем использовать это для анимации узора вместо анимации смещения. Фаза phase пропорциональна ширине, которую мы передали ранее. То есть, 1f соответствует длине одного повторения.

С помощью этой вспомогательной функции мы можем создавать анимированные полосы, например:

val phase by rememberInfiniteTransition()  
    .animateFloat(  
        initialValue = 0f,  
        targetValue = 1f,  
        animationSpec = infiniteRepeatable(  
            animation = tween(  
                durationMillis = 300,  
                easing = LinearEasing,  
            )  
        )  
    )  
  
Box(  
    modifier = Modifier  
        ...
        .drawBehind {  
            drawRect(  
                brush = Brush.stripes(  
                    White to 1f,  
                    Zinc900 to 1f,  
                    width = 10.dp.toPx(),  
                    angle = 45f,  
                    phase = -phase  
                )  
            )  
        }  
)

Благодаря этому мы можем создавать интересные узоры для использования в нашем приложении. Это хорошо работает в загрузчиках или компонентах, где нужно отображать текущее состояние. Или можно использовать это для добавления текстур. Я использовал ту же концепцию в своей статье об CRT-эффектах.

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

Статья

Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.
Telegram

Популярное

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: