Animations are essential in a modern mobile applications to have a smooth user experience.
Jetpack Compose provides powerful and extensible APIs that make it easy to implement various animations in our app’s UI.
Today we are going to implement slightly complex animations in Jetpack Compose.
We are what we repeatedly do. Excellence, then, is not an act, but a habit. Try out Justly and start building your habits today!
We are going to implement four different animations in this blog:
At the end of this article we are going to learn animations like ----
As the name suggests we will have two circles(twins obviously) inside parent circle(with red background) as a part of UI.
Row(
modifier = Modifier
.size(120.dp)
.padding(12.dp)
.clip(CircleShape)
.background(Red),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(15.dp)
.scale(twinCircleAnimation)
.clip(CircleShape)
.background(White)
)
Spacer(modifier = Modifier.width(6.dp))
Box(
modifier = Modifier
.size(15.dp)
.scale(twinCircleAnimation)
.clip(CircleShape)
.background(White)
)
}
Here, we have taken row for a parent circle and inside that we have used two Boxes for our twin circles, we have used spacer two provide space between two twin circles.It is all about UI part of our first animation.
val twinCircleAnimation by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = 7f,
animationSpec = infiniteRepeatable(
animation = tween(1500, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse
)
)
Now, let's talk about twinCircleAnimation which will basically provide animation to our two tiny circles.
val infiniteTransition = rememberInfiniteTransition()
Here, we have used infiniteTransition
and we are remembering it as we need to repeat our animation for an infinite amount of time. Then animateFloat will create the animation of type float that runs infinitely as a part of infiniteTransition
.
Once the animation is created it will run from the initial value (1f) to the target value (7f) and repeat it.
Here, we have used tween as a duration-based AnimationSpec and it uses easing to adjust the animation’s fraction.
Easing allows the animating value to speed up and slow down, rather than moving at a constant speed.
Jetpack compose provides several built-in Easing functions that cover almost all the use cases. Here we have used FastOutSlowInEasing. Another Jetpack composes built-in easing are:
FastOutSlowInEasing
LinearOutSlowInEasing
FastOutLinearInEasing
LinearEasing
CubicBezierEasing
Tween also has another parameter called durationMills
which is the waiting period after each iteration of our animation(we have used 1500 in the Twin animation example).
The complete composable for our Twin circle animation is available on Github.
Here, we have two circles that will move from left to right and vice versa.
Here, we need to observe three things:
When circles move towards offset values, they change their colors, size and when two circles interact with each other they slow down (with the help of LinearOutSlowInEasing).
Canvas(
modifier = Modifier
.padding(top = 16.dp)
.size(100.dp)
) {
drawCircle(
color = Color.LightGray,
)
drawCircle(
color = color2,
radius = 80f + scale * 4f,
center = Offset(-offsetX + this.center.x, this.center.y)
)
drawCircle(
color = color,
radius = 80f + scale * 4f,
center = Offset(offsetX + this.center.x, this.center.y)
)
}
Here, we have used canvas to draw our custom UI, inside canvas we have three circles so the first circle with LightGray color is a steady circle and the other two circles are going to move from left to right and vice versa.
So first we need to have an animation for color as we are changing it while moving with offset values.
val easing = LinearOutSlowInEasing
val color by infiniteTransition.animateColor(
initialValue = Color(0xff712B75),
targetValue = Color(0xFFE4AEC5),
animationSpec = infiniteRepeatable(
animation = tween(1500, easing = easing),
repeatMode = RepeatMode.Restart
)
)
val color2 by infiniteTransition.animateColor(
initialValue = Color(0xFFE4AEC5),
targetValue = Color(0xff712B75),
animationSpec = infiniteRepeatable(
animation = tween(1500, easing = easing),
repeatMode = RepeatMode.Reverse
)
)
It is just the same as our Twin circle animation, where we used animateFloat here as we are animating color, we are using animateColor
. The rest of the things are already covered inside our twin circle animation.
Now, to move two circles on given offset values we have something like:
val offsetX by animateValues(
values = listOf(0f, 100f, -100f, 0f),
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 1500, easing = easing),
repeatMode = RepeatMode.Restart
)
)
Here, our circles will move on offsetX
in the range of (0f,100f,-100f,0f) means 0f to 100f, then 100f to -100f and lastly from -100f to 0f.
Another circle will have the same path but in opposite direction, animateValues
which is composable that will help us change the values based on target positions.
A typical animateValues
composable will look something like this:
@Composable
fun animateValues(
values: List<Float>,
animationSpec: AnimationSpec<Float> = spring(),
): State<Float> {
// 1. Create the groups zipping with next entry
val groups by rememberUpdatedState(newValue = values.zipWithNext())
// 2. Start the state with the first value
val state = remember { mutableStateOf(values.first()) }
LaunchedEffect(key1 = groups) {
val (_, setValue) = state
// Start the animation from 0 to groups quantity
animate(
initialValue = 0f,
targetValue = groups.size.toFloat(),
animationSpec = animationSpec,
) { frame, _ ->
// Get which group is being evaluated
val integerPart = frame.toInt()
val (initialValue, finalValue) = groups[frame.toInt()]
// Get the current "position" from the group animation
val decimalPart = frame - integerPart
// Calculate the progress between the initial and final value
setValue(
initialValue + (finalValue - initialValue) * decimalPart
)
}
}
return state
}
And lastly, to change the size of the circles when they move on given offset values we have animation like:
val scale by animateValues(
values = listOf(1f, 10f, 10f, 10f, 1f),
animationSpec = infiniteRepeatable(
animation = tween(1500, easing = easing),
repeatMode = RepeatMode.Restart
)
)
Here, also we have used animateValues
composable for the same purpose to change the scale values.
The complete composable for circle offset animation is available on Github:
For Pacman animation also we are using canvas to draw Pacman UI.
Canvas(
modifier = Modifier
.padding(top = 16.dp)
.size(100.dp)
) {
drawArc(
color = Color(0xFFFFd301),
startAngle = antiMouthAnimation,
sweepAngle = mouthAnimation,
useCenter = true,
)
drawCircle(
color = Black,
radius = 15f,
center = Offset(x = this.center.x + 15f, y = this.center.y - 85f)
)
}
Here, as we can see inside canvas we have one arc for Pacman and we have one circle that is for the eye of our Pacman.
Inside drawArc
we have used two animations called mouth animation and antimouthAnimation, so when both these animations are used in a single arc there will be animation effects like a mouth opening and closing.
val mouthAnimation by infiniteTransition.animateFloat(
initialValue = 360F,
targetValue = 280F,
animationSpec = infiniteRepeatable(
animation = tween(800, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Restart
)
)
val antiMouthAnimation by infiniteTransition.animateFloat(
initialValue = 0F,
targetValue = 40F,
animationSpec = infiniteRepeatable(
animation = tween(800, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Restart
)
)
Mouth animation will rotate our Pacman arc from 360f to 280f(upper side of mouth) and anti-mouth animation will rotate our Pacman arc from 0f to 40f(lower side of mouth).
The complete composable for Pacman animation is available on Github.
As seen in the snippet below Arc rotation animation also contains a canvas to draw Arc rotation UI.
Canvas(
modifier = Modifier
.padding(12.dp)
.size(100.dp)
) {
drawArc(
color = circleColor,
startAngle = arcAngle1,
sweepAngle = 90f,
useCenter = false,
style = Stroke(width = 10f, cap = StrokeCap.Round),
)
drawArc(
color = circleColor,
startAngle = arcAngle2,
sweepAngle = 90f,
useCenter = false,
style = Stroke(width = 10f, cap = StrokeCap.Round),
)
drawCircle(
color = Color.LightGray,
radius = 120f,
)
drawCircle(
color = circleColor,
radius = greenCircleAnimation,
)
}
As we can see we have two arcs that will rotate continuously towards our circle(Light gray circle). We have one steady circle(Light gray) and one circle with animation (Green circle).
val arcAngle1 by infiniteTransition.animateFloat(
initialValue = 0F,
targetValue = 180F,
animationSpec = infiniteRepeatable(
animation = tween(1000, easing = LinearEasing),
repeatMode = RepeatMode.Restart
)
)
We have used the above animation in our first arc, which will draw the arc at a start angle 0f
initially and then after continuously it will change its start angle until 180f so we will have effect like arc rotation.
val arcAngle2 by infiniteTransition.animateFloat(
initialValue = 180F,
targetValue = 360F,
animationSpec = infiniteRepeatable(
animation = tween(1000, easing = LinearEasing),
repeatMode = RepeatMode.Restart
)
)
The above animation will be used in our second arc, which will draw the arc at a start angle 180f
initially, and then after continuously it will change its start angle until 360f
so we will have an effect like arc rotation.
We have used LinearEasing for both the arcs as we want to have a constant rotation speed for both arcs.
When we observe both the arc simultaneously we will have an effect like both arcs rotating in synchronization in a clockwise direction.
val greenCircleAnimation by infiniteTransition.animateFloat(
initialValue = 50f,
targetValue = 80f,
animationSpec = infiniteRepeatable(
animation = tween(1000, delayMillis = 100, easing = FastOutLinearInEasing),
repeatMode = RepeatMode.Reverse
)
)
And lastly, for our circle with animation, we have used the above animation with FastOutLinearEasing.
The complete composable for Arc rotation animation is available on Github.
That’s it for all the Animations…
Don’t worry if you didn’t get all the animations.
You can find the complete source code of all the above animations on Github.
If you love these animations, here are few more for you written by my colleague, these animations are also available in above mentioned Github repository.
Let's Work Together
Not sure where to start? We also offer code and architecture reviews, strategic planning, and more.