Разработка
Паря в космосе: анимации с помощью Compose и Canvas
Как мы видим, даже небольшие изменения придают холсту большую подвижность.
В своей предыдущей статье «Рисуя звезды: рисование с помощью Compose и Canvas» я рассказала о том, как я хотела улучшить свои навыки работы с Canvas и Compose и создала иллюстрацию с планетами и звездами. В этой статье я расскажу, как анимировать эти элементы. В итоге результат будет выглядеть так:
Весь код доступен здесь.
Анимация
Звезды
Давайте начнем со звезд. В предыдущей статье мы определили класс данных Star следующим образом:
data class Star(
val size: Float,
val topLeft: Offset,
val color: Color,
)
Исходя из этих значений, мы хотим изменить размер звезд. Для этого мы определим множитель, который будем использовать вместе с размерами звезд для создания эффекта мерцания. Мы сделаем это с помощью infiniteTransition
и animateFloat
:
xxxxxxxxxx
val infiniteTransition =
rememberInfiniteTransition(label = "infinite")
val starSizeMultiplierOne by infiniteTransition
.animateFloat(
initialValue = 1f,
targetValue = 1.5f,
animationSpec =
infiniteRepeatable(
animation =
tween(
durationMillis = 3000,
easing = animationEasing,
),
repeatMode = RepeatMode.Reverse,
),
label = "starSizeMultiplierOne",
)
Для starSizeMultiplierOne
мы зададим начальное значение 1, что означает, что, поскольку он будет действовать как множитель, размер будет равен 1 * size
. Целевое значение — 1.5, а поскольку режим повтора — Reverse
, звезды будут колебаться в размере между 1 и 1.5. Этот переход создает эффект увеличения и уменьшения.
Поскольку мы хотим, чтобы звезды выглядели реалистично и не анимировались с одинаковой скоростью, нам нужно определить два анимированных множителя. Давайте определим еще один:
xxxxxxxxxx
val starSizeMultiplierTwo by infiniteTransition
.animateFloat(
initialValue = 0.7f,
targetValue = 1.7f,
animationSpec =
infiniteRepeatable(
animation =
tween(
durationMillis = 2300,
easing = animationEasing,
),
repeatMode = RepeatMode.Reverse,
),
label = "starSizeMultiplierTwo",
)
Этот мультипликатор имеет немного меньшую продолжительность анимации, а начальные и целевые значения отличаются, поэтому мерцание происходит с другой скоростью, чем в первом мультипликаторе.
Теперь нам нужно передать эти множители звездам:
xxxxxxxxxx
val stars =
starsList.mapIndexed { index, star ->
val multiplier = if (index % 2 == 0)
starSizeMultiplierOne
else
starSizeMultiplierTwo
star.copy(
size = star.size * multiplier,
)
}
Мы просматриваем список звезд, определенный ранее, возвращаем копии звезд с прикрепленным к ним множителями и сохраняем их в новом списке. Позже, при рисовании звезд, мы используем этот новый список.
В случае этого фрагмента кода такой перебор списка может показаться излишним — почему бы просто не определить множители при определении звезд? В данном случае это сработает. Но если бы мы определяли звезды вне компонента, анимированные значения было бы сложно подключить, поэтому стратегия будет именно такой.
Планета
Следующая вещь, которую мы хотим анимировать, — это планета, точнее, луна, вращающаяся вокруг нее.
В предыдущем посте мы определили, как рисовать планету и луну, а в исходном коде была показана функция расширения под названием drawMoon
. Давайте расширим ее и передадим параметр под названием degrees
:
xxxxxxxxxx
fun DrawScope.drawMoon(
center: Offset,
outlineStyle: Stroke,
degrees: Float,
) {
...
}
Затем мы обернем содержимое drawMoon
функцией rotate
, которая использует градусы для вращения и центр для поворота, чтобы вращение происходило вокруг центра планеты:
xxxxxxxxxx
rotate(degrees = degrees, pivot = center) {
...
}
В компоненте верхнего уровня мы определяем degrees
, которые представляют собой значения смещения анимации:
xxxxxxxxxx
val degrees by infiniteTransition
.animateFloat(
initialValue = 360f,
targetValue = 0f,
animationSpec =
infiniteRepeatable(
animation =
tween(
durationMillis = 3000 * 6,
easing = LinearEasing,
),
),
label = "degrees",
)
В качестве начального значения мы используем 360f, а в качестве целевого — 0f. Эти значения создают эффект, что луна вращается вокруг планеты против часовой стрелки.
Сатурн
Последний элемент, который мы анимируем, — это Сатурн. Его движение очень тонкое — он слегка перемещается вверх и вниз, создавая эффект парения.
Ранее мы определили функцию расширения drawSaturn
, которая принимает координаты левого верхнего угла и стиль контура. Мы можем использовать левые верхние координаты для создания эффекта.
Сначала давайте определим смещение центра — значение анимации, которое мы будем использовать:
xxxxxxxxxx
val centerOffset by infiniteTransition
.animateFloat(
initialValue = 0f,
targetValue = 2f,
animationSpec =
infiniteRepeatable(
animation =
tween(
durationMillis = 3000,
easing = EaseIn,
),
repeatMode = RepeatMode.Reverse,
),
label = "centerOffset",
)
Как видите, начальное значение равно 0f, а целевое — 2f. Мы можем использовать это и изменить параметры, передаваемые в drawSaturn
, добавив centerOffset
к координатам левого верхнего угла:
xxxxxxxxxx
drawSaturn(
center =
Offset(
size.width * 0.25f + centerOffset,
size.height * 0.25f + centerOffset
),
outlineStyle = outlineStyle,
)
Таким образом, координаты x и y получают дополнительную анимацию от 0f до 2f, создавая эффект парения.
Подведение итогов
В этой статье мы рассмотрели анимацию рисунков на Canvas. Во всех анимациях по-разному использовались эффекты анимации. И, как мы видим, даже небольшие изменения придают холсту большую подвижность.
Надеюсь, вам понравилась эта статья и вы чему-то научились!
-
Новости2 недели назад
Видео и подкасты о мобильной разработке 2025.14
-
Видео и подкасты для разработчиков4 недели назад
Javascript для бэкенда – отличная идея: Node.js, NPM, Typescript
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2025.12
-
Разработка3 недели назад
«Давайте просто…»: системные идеи, которые звучат хорошо, но почти никогда не работают