Welcome ๐
In this article, we will create a stunning 3D Atomic Loader animation in Jetpack Compose.
Excited? ๐คฉ Letโs dive in! โ๏ธ๐
Inspiration โจ
This loader animation is inspired by the original work created by Martin van Driel using HTML and CSS, which is licensed under the MIT License.
I recreated this effect using Jetpack Compose to bring it to Android. Enjoy the tutorial!
Creating the Rotating Circle
To start, we need to create a RotatingCircle
composable function. This function will render a single circle, which weโll use to compose the loader.
@Composable
fun RotatingCircle(
modifier: Modifier,
rotationX: Float,
rotationY: Float,
rotationZ: Float,
borderColor: Color,
borderWidth: Dp
) {
// Further implementation
}
Drawing the Circle
To achieve the desired effect, we subtract one circle from another and draw the result on a Canvas
:
Canvas(modifier) {
// Define the path for the main circle
val mainCircle = Path().apply {
addOval(Rect(size.center, size.minDimension / 2))
}
// Adjust the position of the clipping circle to the left by borderWidth
val clipCenter = Offset(size.width / 2f - borderWidth.toPx(), size.height / 2f)
// Define the path for the clipping circle
val clipCircle = Path().apply {
addOval(Rect(clipCenter, size.minDimension / 2))
}
// Subtract the clipping circle from the main circle
val path = Path().apply {
op(mainCircle, clipCircle, PathOperation.Difference)
}
// Draw the subtracted path
drawPath(path, borderColor)
}
Transformation
Next, we use graphicsLayer
to rotate the Canvas
in a 3-dimensional space:
Canvas(
modifier.graphicsLayer {
this.rotationX = rotationX
this.rotationY = rotationY
this.rotationZ = rotationZ
// To create a depth effect adjust the cameraDistance
cameraDistance = 12f * density
}
) {
// Drawing logic
}
Composing the Loader
To render the loader, we define theAtomicLoader
composable function:
@Composable
fun AtomicLoader(
modifier: Modifier,
color: Color = Color.White,
borderWidth: Dp = 3.dp,
cycleDuration: Int = 1000
)
Parameters
โ๏ธ modifier
๐ Modifier to be applied to the loader container.
โ๏ธ color
๐ Color of the loader.
โ๏ธ borderWidth
๐ Width of the loaderโs border.
โ๏ธ cycleDuration
๐ Duration of one complete rotation cycle in milliseconds.
Creating the Rotation Animation
To animate the loader, we define an infinite rotation that cycles from 0 to 360 degrees:
val infiniteTransition = rememberInfiniteTransition("InfiniteAtomicLoaderTransition")
val rotation by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(cycleDuration, easing = LinearEasing)
),
label = "AtomicLoaderRotation"
)
Placing the Circles
Finally, we position the circles inside the Box
composable and apply the rotation animation:
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
)
}
Congratulations ๐ฅณ! Weโve successfully built it ๐. You can find the full code on GitHub Gist ๐งโ๐ป. Letโs explore the usage ๐
Practical Usage ๐โโ๏ธ
Letโs create a Box
with a radial gradient background and place the loader at the center to see the result.
โ ๏ธ Ensure the loader has a clearly defined size! If not, it might not appear or could cause unexpected behavior.
Box(
modifier = Modifier
.fillMaxSize()
.background(
brush = Brush.radialGradient(
listOf(Color(0xFF3C4B57), Color(0xFF1C262B))
)
),
contentAlignment = Alignment.Center
) {
AtomicLoader(
Modifier.size(100.dp)
)
}
Output ๐
Thank you for reading this article! โค๏ธ If you found it enjoyable and valuable, show your appreciation by clapping ๐ and following Kappdev for more exciting articles ๐