Из этой статьи вы узнаете, как создать 3D-анимацию атомарного загрузчика (atomic loader) в Jetpack Compose.

Создано автором

Источник вдохновения 

Эта анимация загрузчика вдохновлена оригинальной работой, выполненной с использованием HTML и CSS Мартином ван Дриелем и имеющей лицензию от Массачусетского технологического института.

Я воссоздал этот эффект с помощью Jetpack Compose, чтобы перенести его на Android.

Создание вращающегося круга

Для начала нужно создать composable-функцию RotatingCircle. Эта функция рендерит один круг, который будет использоваться для создания загрузчика.

@Composable
fun RotatingCircle(
modifier: Modifier,
rotationX: Float,
rotationY: Float,
rotationZ: Float,
borderColor: Color,
borderWidth: Dp
) {
// Дальнейшая реализация
}

Рисование круга

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

Canvas(modifier) {
// Определение пути для главного круга
val mainCircle = Path().apply {
addOval(Rect(size.center, size.minDimension / 2))
}

// Корректировка позиции отделяемого круга слева от borderWidth
val clipCenter = Offset(size.width / 2f - borderWidth.toPx(), size.height / 2f)
// Определение пути для отделяемого круга
val clipCircle = Path().apply {
addOval(Rect(clipCenter, size.minDimension / 2))
}

// Вычленение отделяемого круга из главного круга
val path = Path().apply {
op(mainCircle, clipCircle, PathOperation.Difference)
}

// Рисование отделяемого пути
drawPath(path, borderColor)
}
Вычленение

Трансформация

Теперь используем graphicsLayer для поворота Canvas в трехмерном пространстве:

Canvas(
modifier.graphicsLayer {
this.rotationX = rotationX
this.rotationY = rotationY
this.rotationZ = rotationZ
// Для создания эффекта глубины настройте cameraDistance
cameraDistance = 12f * density
}
) {
// Логика рисования
}

Создание загрузчика

Для рендеринга загрузчика определяем composable-функцию AtomicLoader:

@Composable
fun AtomicLoader(
modifier: Modifier,
color: Color = Color.White,
borderWidth: Dp = 3.dp,
cycleDuration: Int = 1000
)

Параметры:

  • modifier: модификатор, который будет применен к контейнеру загрузчика;
  • color: цвет загрузчика;
  • borderWidth: ширина границы загрузчика;
  • cycleDuration: продолжительность одного полного цикла вращения в миллисекундах.

Создание анимации вращения

Чтобы анимировать загрузчик, определим бесконечное вращение, которое будет проходить цикл от 0 до 360 градусов:

val infiniteTransition = rememberInfiniteTransition("InfiniteAtomicLoaderTransition")

val rotation by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(cycleDuration, easing = LinearEasing)
),
label = "AtomicLoaderRotation"
)

Размещение кругов

Наконец, разместим круги внутри composable Box и применим анимацию вращения:

Box(modifier) {
RotatingCircle(
modifier = Modifier.matchParentSize(),
rotationX = 35f,
rotationY = -45f,
rotationZ = -90f + rotation,
borderColor = color,
borderWidth = borderWidth
)
RotatingCircle(
modifier = Modifier.matchParentSize(),
rotationX = 50f,
rotationY = 10f,
rotationZ = rotation,
borderColor = color,
borderWidth = borderWidth
)
RotatingCircle(
modifier = Modifier.matchParentSize(),
rotationX = 35f,
rotationY = 55f,
rotationZ = 90f + rotation,
borderColor = color,
borderWidth = borderWidth
)
}

Анимация успешно создана. Полный код можно найти на GitHub Gist. А пока рассмотрите возможности использования созданной анимации.

Практическое применение 

Создадим Box с радиальным градиентным фоном и поместим загрузчик в центр, чтобы увидеть результат.

Убедитесь, что загрузчик имеет четко определенный размер! В противном случае он может не отобразиться или демонстрировать неожиданное поведение.

Box(
modifier = Modifier
.fillMaxSize()
.background(
brush = Brush.radialGradient(
listOf(Color(0xFF3C4B57), Color(0xFF1C262B))
)
),
contentAlignment = Alignment.Center
) {
AtomicLoader(
Modifier.size(100.dp)
)
}

Вывод

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Kappdev: How to Create an Atomic Loader in Jetpack Compose

Предыдущая статьяNext.js: шаблоны управления состоянием через React Server Components  
Следующая статьяЭффективные шаблоны архитектуры программного обеспечения