Progress Animations in Jetpack Compose with examples

Learn to implement complex animations in Jetpack Compose for a smooth user experience in modern mobile apps!
Mar 30 2022 · 7 min read

Introduction 

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:

  1. Twin circle animation : This animation contains two white circles that will be having zoom-in effect simultaneously, and they will ultimately hide parent element and then they will follow same animation in reverse order(zoom out).
  2. Circle offset animation: This animation again contains two circles and this time they will move from left to right and vice versa(offset animation) While offsetting from left to right or right to left when two circles interacts with each other the offset animation speed will slightly decrease providing the magnet like effect.
  3. Pacman animation: We all played Pacman game during our childhood. This animation is exactly the same as Pacman. Here, in this animation Pacman will open and close his mouth in repeated order(not to eat food).
  4. Arc rotation animation: In this animation we will have two arcs that will rotate continuously in clockwise direction. At the center of the Arc we will have two circles one circle will stay steady while other will zoom in and zoom out repeatedly.

At the end of this article we are going to learn animations like ----

Cool Progress animations

Let’s start with the animations!

1. Twin circle animation

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.

2. Circle offset animation

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:

3. Pacman animation

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.

4. Arc rotation animation

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.

Conclusion 

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.


Code, Build, Repeat.
Stay updated with the latest trends and tutorials in Android, iOS, and web development.
jimmy image
Jimmy Sanghani
Jimmy Sanghani is a tech nomad and cofounder at Canopas helping businesses use new age leverage - Code and Media - to grow their revenue exponentially. With over a decade of experience in both web and mobile app development, he has helped 100+ clients transform their visions into impactful digital solutions. With his team, he's helping clients navigate the digital landscape and achieve their objectives, one successful project at a time.
jimmy image
Jimmy Sanghani
Jimmy Sanghani is a tech nomad and cofounder at Canopas helping businesses use new age leverage - Code and Media - to grow their revenue exponentially. With over a decade of experience in both web and mobile app development, he has helped 100+ clients transform their visions into impactful digital solutions. With his team, he's helping clients navigate the digital landscape and achieve their objectives, one successful project at a time.
canopas-logo
We build products that customers can't help but love!
Get in touch

Let's Work Together

Not sure where to start? We also offer code and architecture reviews, strategic planning, and more.

cta-image
Get Free Consultation
footer
Follow us on
2024 Canopas Software LLP. All rights reserved.