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 π