Animation in Android Jetpack Compose | Animatable with Examples

Jetpack Compose Animation

Jetpack Compose has several animation APIs. Animatable is one of them. In this article, we will learn what it is and how to use it.

Prerequisites:

For this article, we will play with a Box(). First, create an empty Jetpack Compose project and declare a BoxSizeAnimation() composable outside the MainActivity class.

@Composable
fun BoxSizeAnimation() {
}

Remove the Greeting() and DefaultPreview() composables from the MainActivity. Remove the Surface() from the onCreate() and add a Column(). Call our BoxSizeAnimation() in it. Final MainActivity.kt looks like this:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            YourProjectNameTheme(darkTheme = false) {
                Column(
                    modifier = Modifier.fillMaxSize(),
                    verticalArrangement = Arrangement.Center,
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    BoxSizeAnimation()
                }
            }
        }
    }
}

@Composable
fun BoxSizeAnimation() {
}

Let’s create a red color box with 40 dp size.

@Composable
fun BoxSizeAnimation() {
    Box(
        modifier = Modifier
            .size(size = 40.dp)
            .background(color = Color.Red)
    )
}

We will animate the size of the Box() using Animatable. We can get the Animatable object using remember composable.

val scale = remember {
    Animatable(initialValue = 1f)
}

Add the Modifier.scale() to the Box().

@Composable
fun BoxSizeAnimation() {

    val scale = remember {
        Animatable(initialValue = 1f)
    }

    Box(
        modifier = Modifier
            .scale(scale = scale.value)
            .size(size = 40.dp)
            .background(color = Color.Red)
    )
}

Let’s animate the box when we tap on it. Add Modifier.clickable function.

@Composable
fun BoxSizeAnimation() {
    
    val scale = remember {
        Animatable(initialValue = 1f)
    }

    Box(
        modifier = Modifier
            .scale(scale = scale.value)
            .size(size = 40.dp)
            .background(color = Color.Red)
            .clickable {

            }
    )
}

Animatable animates the value that is changed via animateTo function. It is a suspend function. So, we need a coroutine scope. We can get the scope by using rememberCoroutineScope().

val coroutineScope = rememberCoroutineScope()

Let’s make the box 4 times bigger. Inside the clickable block, launch the coroutine and call the animateTo() with targetValue = 4f.

@Composable
fun BoxSizeAnimation() {
    val coroutineScope = rememberCoroutineScope()

    val scale = remember {
        Animatable(initialValue = 1f)
    }

    Box(
        modifier = Modifier
            .scale(scale = scale.value)
            .size(size = 40.dp)
            .background(color = Color.Red)
            .clickable {
                coroutineScope.launch {
                    scale.animateTo(targetValue = 4f)
                }
            }
    )
}

Output:

Jetpack Compose Animatable

The animateTo() function has animationSpec parameter. It allows us to customize the animation.

The following are the different kinds of AnimationSpec APIs.

  • tween
  • spring
  • keyframes
  • repeatable
  • infiniteRepeatable
  • snap

tween:

It accepts 3 parameters – durationMillis, delayMillis, and easing.

durationMillis – The duration of the animation in milliseconds.

delayMillis – You can postpone (or delay) the animation.

easing – It allows us to change the animation’s rate of change. We can speed up and slow down the animation value, rather than moving at a constant rate.

Here is an example:

scale.animateTo(
    targetValue = 4f,
    animationSpec = tween(
        durationMillis = 2000,
        delayMillis = 1000,
        easing = FastOutLinearInEasing
    )
)

Output:

tween animation

When you tap on the box, the animation will be started after 1 second and continued for 2 seconds. FastOutLinearInEasing starts the animation slowly and ends quickly.

Jetpack Compose provides several Easing functions out of the box. They are:

  • FastOutLinearInEasing
  • FastOutSlowInEasing
  • LinearOutSlowInEasing
  • FastOutLinearEasing
  • LinearEasing
  • CubicBezierEasing

You can learn more about them here: https://material.io/design/motion/speed.html#easing

spring:

It creates a physics-based animation. It takes 2 parameters: dampingRatio and stiffness.

dampingRatio – It decides the bouncing of the animation.

stiffness – It decides how fast the spring should move toward the end value.

scale.animateTo(
    targetValue = 4f,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioHighBouncy,
        stiffness = Spring.StiffnessHigh
    )
)

Output:

spring animation spec

The animation is completed quickly because we have set DampingRatioHighBouncy and StiffnessHigh.

Here are the values that Jetpack Compose provides out of the box:

dampingRatio:

  • Spring.DampingRatioNoBouncy
  • Spring.DampingRatioLowBouncy
  • Spring.DampingRatioMediumBouncy
  • Spring.DampingRationHighBouncy

stiffness:

  • Spring.StiffnessHigh
  • Spring.StiffnessMedium
  • Spring.StiffnessMediumLow
  • Spring.StiffnessLow
  • Spring.StiffnessVeryLow

Learn more about spring here: https://developer.android.com/jetpack/compose/animation#spring

keyframes:

We can include keyframes. We can also specify Easing for each keyframe.

scale.animateTo(
    targetValue = 4f,
    animationSpec = keyframes {
        durationMillis = 4000
        1f at 1000 with LinearOutSlowInEasing // 0 - 1000 ms
        2f at 1100 with FastOutLinearInEasing // 1000 - 1100 ms
        3f at 1200 // 1100 - 1200 ms
        4f at 4000 // 1200 - 4000 ms
    }
)

Output:

keyframes jetpack compose

In the above code, we have set the overall duration to 4 seconds (durationMillis = 4000). Our initial value of the scale is 1f. We have added 4 keyframes.

1f at 1000 with LinearOutSlowInEasing:

From 0 to 1000 ms, the scale value is increased from the initial value to 1f. Since the initial value is also 1f, we haven’t seen any animation during the first 1000 ms.

2f at 1100 with FastOutLinearInEasing:

From 1000 to 1100 ms, the scale value is increased from 1f to 2f (with FastOutLinearInEasing).

3f at 1200:

From 1100 to 1200 ms, the scale value is increased from 2f to 3f.

4f at 4000:

From 1200 to 4000 ms, the scale value is increased from 3f to 4f.

Note: You don’t have to mention the values at 0 ms and at the duration time. If you don’t specify these values, they default to the start and end values of the animation, respectively.

repeatable:

We can repeat the animation using repeatable(). It takes parameters like iterations, animation, and repeatMode.

iterations – How many times the animation should be repeated (it is an integer value).

animation – It is the animation that should be repeated. You can specify duration-based animations (such as tween or keyframes)

repeatMode – Whether the animation should repeat by starting from the beginning (RepeatMode.Restart) or from the end (RepeatMode.Reverse).

scale.animateTo(
    targetValue = 4f,
    animationSpec = repeatable(
        iterations = 3,
        animation = tween(durationMillis = 1000),
        repeatMode = RepeatMode.Reverse
    )
)

Output:

repeat animation

infiniteRepeatable:

It is the same as the repeatable() but it animates forever.

scale.animateTo(
    targetValue = 4f,
    animationSpec = infiniteRepeatable(
        animation = tween(durationMillis = 1000),
        repeatMode = RepeatMode.Reverse
    )
)

Output:

repeat forever infinite

Note: To stop the animation call scale.stop() method.

snap:

It changes the value to the end value immediately. You can specify the delay for the animation using delayMillis.

scale.animateTo(
    targetValue = 4f,
    animationSpec = snap(delayMillis = 1000)
)

Output:

snap

One of the best things about Animatable is that we can run multiple animations one after the other or at the same time.

Let us animate the color of the box after the size animation is finished. First, declare a color variable.

val color = remember {
    Animatable(initialValue = Color.Red)
}

Set this color in the Modifer.background() function.

 Box(
        modifier = Modifier
            .scale(scale = scale.value)
            .size(size = 40.dp)
            .background(color = color.value)
            .clickable {
            }
    )

In the clickable block, launch a coroutine and call scale.animateTo() and color.animateTo() methods one after the other.

coroutineScope.launch {
    scale.animateTo(
        targetValue = 4f,
        animationSpec = tween(durationMillis = 2000)
    )

    color.animateTo(
        targetValue = Color.Yellow,
        animationSpec = tween(durationMillis = 2000)
    )
}

Output:

Jetpack compose animation one after another 2

What if you want to animate both the size and the color simultaneously? Just put the animateTo() functions in separate launch blocks.

coroutineScope.launch {
    launch {
        scale.animateTo(
            targetValue = 4f,
            animationSpec = tween(durationMillis = 2000)
        )
    }

    launch {
        color.animateTo(
            targetValue = Color.Yellow,
            animationSpec = tween(durationMillis = 2000)
        )
    }
}

Output:

parallel animation

4 thoughts on “Animation in Android Jetpack Compose | Animatable with Examples”

Leave a Comment