В сфере разработки современных приложений плавная и визуально привлекательная анимация загрузки имеет решающее значение для пользовательского интерфейса. Jetpack Compose, известный своим декларативным UI-подходом, предлагает мощные инструменты для создания таких анимаций.
В этой статье мы разработаем компонент Custom Circle Loader (кастомизированный круговой загрузчик) с помощью Jetpack Compose. Присоединяйтесь, чтобы открыть для себя новые возможности.
![](https://nuancesprog.ru/wp-content/uploads/2024/07/image-12.gif)
Настройка
Начнем с создания класса данных StrokeStyle
, необходимого для работы со стилем обводки загрузчика.
data class StrokeStyle(
val width: Dp = 4.dp,
val strokeCap: StrokeCap = StrokeCap.Round,
val glowRadius: Dp? = 4.dp
)
Создание функции
Теперь определим и реализуем composable-функцию CircleLoader
.
Сигнатура функции
@Composable
fun CircleLoader(
modifier: Modifier,
isVisible: Boolean,
color: Color,
secondColor: Color? = color,
tailLength: Float = 140f,
smoothTransition: Boolean = true,
strokeStyle: StrokeStyle = StrokeStyle(),
cycleDuration: Int = 1400,
)
Объяснение параметров
modifier
➜ модификатор, применяемый к composable-компонентуCanvas
и позволяющий настраивать макет и внешний вид;isVisible
➜ определяет видимость анимации загрузчика;color
➜ определяет основной цвет загрузчика;secondColor
➜ определяет опциональный дополнительный цвет загрузчика;tailLength
➜ определяет угол поворота “хвоста” загрузчика, измеряемый в градусах;smoothTransition
➜ определяет степень плавности перехода между состояниями видимости загрузчика;strokeStyle
➜ определяет стиль обводки, используемой при рисовании загрузчика;cycleDuration
➜ определяет длительность цикла анимации загрузчика, измеряемую в миллисекундах.
Реализация
После определения функции перейдем к этапу выполнения, чтобы реализовать анимацию.
Работа с объектом paint
Функция setupPaint
настраивает объект paint, используемый для рисования. Он применяет стиль обводки и настройки кисти.
fun DrawScope.setupPaint(style: StrokeStyle, brush: Brush): Paint {
val paint = Paint().apply paint@{
[email protected] = true
[email protected] = PaintingStyle.Stroke
[email protected] = style.width.toPx()
[email protected] = style.strokeCap
brush.applyTo(size, this@paint, 1f)
}
style.glowRadius?.let { radius ->
paint.asFrameworkPaint().setShadowLayer(
/* радиус = */ radius.toPx(),
/* dx = */ 0f,
/* dy = */ 0f,
/* shadowColor = */ android.graphics.Color.WHITE
)
}
return paint
}
Магия анимации
Загрузчик имеет два типа анимации.
Анимация вращения (Rotation Animation): используется rememberInfiniteTransition
для создания эффекта бесконечного вращения загрузчика.
val transition = rememberInfiniteTransition()
val spinAngel by transition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = cycleDuration,
easing = LinearEasing
),
repeatMode = RepeatMode.Restart
)
)
Анимация перехода состояния (State Transition Animation): используется Animatable
для придания плавного перехода длине хвоста, имитируя эффект плавного появления и исчезновения; срабатывает при изменении состояния видимости и установке smoothTransition
в true
— в противном случае привязывает значение.
val tailToDisplay = remember { Animatable(0f) }
LaunchedEffect(isVisible) {
val targetTail = if (isVisible) tailLength else 0f
when {
smoothTransition -> smoothTransition -> tailToDisplay.animateTo(
targetValue = targetTail,
animationSpec = tween(cycleDuration, easing = LinearEasing)
)
else -> tailToDisplay.snapTo(targetTail)
}
}
Рисование в Canvas
Пришло время использовать то, что мы создали, для рисования загрузчика. Воспользуемся методом drawArc
, чтобы создать хвост загрузчика.
Canvas(
modifier
// Применение анимации вращения
.rotate(spinAngel)
// Убедитесь, что CircleLoader сохраняет квадратное соотношение сторон
.aspectRatio(1f)
) {
// Итерация по ненулевым цветам
listOfNotNull(color, secondColor).forEachIndexed { index, color ->
// Если это не основной цвет, поворачиваем Canvas на 180 градусов.
rotate(if (index == 0) 0f else 180f) {
// Создание градиентной кисти для загрузчика
val brush = Brush.sweepGradient(
0f to Color.Transparent,
tailToDisplay.value / 360f to color,
1f to Color.Transparent
)
// Настройка объекта paint
val paint = setupPaint(strokeStyle, brush)
// Рисование хвоста загрузчика
drawIntoCanvas { canvas ->
canvas.drawArc(
rect = size.toRect(),
startAngle = 0f,
sweepAngle = tailToDisplay.value,
useCenter = false,
paint = paint
)
}
}
}
}
Поздравляю! Загрузчик успешно создан (полный код реализации можно найти на GitHub Gist). Осталось только научиться им пользоваться.
Использование
Манипулируя основным и дополнительным цветами, можно создать три вида анимации. Посмотрим, как этого добиться.
Во всех примерах будем использовать кнопку (button) и состояние (state) для переключения анимации:
var isLoading by remember { mutableStateOf(false) }
/* Здесь - код для CircleLoader ... */
Button(
onClick = { isLoading = !isLoading }
) {
Text(text = if (isLoading) "Stop" else "Start")
}
1. Одноцветный двойной хвост
Чтобы получить этот эффект, нужно выставить основной цвет, а дополнительный по умолчанию будет таким же.
CircleLoader(
color = Color(0xFF1F79FF),
modifier = Modifier.size(100.dp),
isVisible = isLoading
)
Вывод:
![](https://nuancesprog.ru/wp-content/uploads/2024/07/image-10.gif)
2. Двойной хвост с разными цветами
Чтобы добиться такого эффекта, нужно указать дополнительный цвет.
CircleLoader(
color = Color(0xFF1F79FF),
secondColor = Color(0xFFFFE91F),
modifier = Modifier.size(100.dp),
isVisible = isLoading
)
Вывод:
![](https://nuancesprog.ru/wp-content/uploads/2024/07/image-9.gif)
3. Одинарный хвост
Для получения эффекта одинарного хвоста просто установите дополнительный цвет в null
. Кроме того, при желании можно увеличить длину хвоста.
CircleLoader(
color = Color(0xFF1F79FF),
secondColor = null,
tailLength = 280f,
modifier = Modifier.size(100.dp),
isVisible = isLoading
)
Вывод:
![](https://nuancesprog.ru/wp-content/uploads/2024/07/image-11.gif)
Читайте также:
- Освоение различных видов линий в Jetpack Compose с помощью PathEffect
- Начало работы с базой данных Room в Android
- Введение в Navigation Architecture Component
Читайте нас в Telegram, VK и Дзен
Перевод статьи Kappdev: Building Custom Circle Loader in Jetpack Compose: Exploring Android Canvas and Animations