Lottie Animations in Jetpack Compose (with Examples)

Jetpack Compose Lottie Animations

In this article, we’ll learn how to implement Lottie animations in Jetpack Compose.

Prerequisites:

What is a Lottie?

Lottie is a JSON-based file that helps us to display animations on any device, such as Android or the web, as easily as displaying an image. Lottie animations are created in Adobe After Effects and exported as JSON files.

Advantages of Lottie Files:

  • They are small and lightweight.
  • They are vector-based, so they can be scaled up and down without losing the quality.
  • You can use Lottie animations on iOS, Android, Web, and React Native without modification.

You can download the animations from lottiefiles.com.

For this article, we’ll use this running man animation. You can download it by clicking the “Download” button in the top right corner.

Download Lottie JSON File

Click on the “Lottie JSON” option and the file will be downloaded to your computer. Change its name to running_man.json.

Next, create an empty Jetpack Compose project and the following dependency in the app-level gradle file.

// lottie animation
implementation 'com.airbnb.android:lottie-compose:6.1.0'

Don’t forget to sync the project. Next, open MainActivity, create a MyUI() composable and call it from the onCreate() method. We’ll write our code in it.

MainActivity for Material 3 Jetpack Compose:

// add the following packages
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieClipSpec
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            YourProjectNameTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Column(
                        modifier = Modifier.fillMaxSize(),
                        horizontalAlignment = Alignment.CenterHorizontally,
                        verticalArrangement = Arrangement.Center
                    ) {
                        MyUI()
                    }
                }
            }
        }
    }
}

@Composable
fun MyUI() {

}

For the Material 2 version:

// add the following packages
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieClipSpec
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition

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

@Composable
fun MyUI() {

}

Note: I am using the Material 3 Jetpack Compose. If you are working with Material 2, you may see a slightly different appearance, but the code and animation will still work as expected.

We need to add our running_man JSON file to the project. To do this, create a raw folder in the res directory, and paste the file into the folder.

raw Folder Android Project

We have set up the project. Let’s see how to implement the animations.

Simple Lottie Animation:

@Composable
fun MyUI() {
    val composition by rememberLottieComposition(
        spec = LottieCompositionSpec.RawRes(resId = R.raw.running_man)
    )

    // render the animation
    LottieAnimation(
        modifier = Modifier.size(size = 240.dp),
        composition = composition,
    )

    Spacer(modifier = Modifier.height(height = 12.dp))

    Text(
        text = "Lottie Animation",
        fontSize = 28.sp
    )
}

Output:

Simple Lottie Animation

The rememberLottieComposition() is used to load the animation from our JSON file. The LottieAnimation() displays the animation on the screen. Currently, the animation plays only once. Let’s set it to loop continuously.

Infinite Animation:

The LottieAnimation() takes iterations parameter. It decides how many times the animation should play. The default value is 1. That is why we have seen the animation only once. If we pass LottieConstants.IterateForever, the animation will play continuously. The IterateForever value is nothing but Integer.MAX_VALUE.

@Composable
fun MyUI() {
    val composition by rememberLottieComposition(
        spec = LottieCompositionSpec.RawRes(resId = R.raw.running_man)
    )

    // render the animation
    LottieAnimation(
        modifier = Modifier.size(size = 240.dp),
        composition = composition,
        iterations = LottieConstants.IterateForever // animate forever
    )

    Text(
        text = "Lottie Animation",
        fontSize = 28.sp
    )
}

Output:

Lottie Animating Forever

Trim/Cut the Animation:

We can display the animation between two points using the clipSpec parameter of the LottieAnimation().

@Composable
fun MyUI() {
    val composition by rememberLottieComposition(
        spec = LottieCompositionSpec.RawRes(resId = R.raw.running_man)
    )

    // render the animation
    LottieAnimation(
        modifier = Modifier.size(size = 240.dp),
        composition = composition,
        iterations = LottieConstants.IterateForever,
        clipSpec = LottieClipSpec.Progress(0.5f, 0.75f)
    )

    Text(
        text = "Lottie Animation",
        fontSize = 28.sp
    )
}

Output:

Trim Cut Animation

The Progress() data class takes two values: min and max.

data class Progress(
    val min: Float = 0f,
    val max: Float = 1f,
) : LottieClipSpec() 

The value 0f indicates the beginning of the animation, while the value 1f indicates the end. For example, if the animation duration is 3 seconds, and we set min = 0.5f and max = 1f, the animation will play from 1.5 seconds to 3 seconds.

Animation Speed:

The speed parameter controls the playback speed. By default, it is 1f. Increasing the value above 1f will result in a faster animation playback while decreasing it below 1f will make the animation play slower.

Let’s double the speed.

@Composable
fun MyUI() {
    val composition by rememberLottieComposition(
        spec = LottieCompositionSpec.RawRes(resId = R.raw.running_man)
    )

    // render the animation
    LottieAnimation(
        modifier = Modifier.size(size = 240.dp),
        composition = composition,
        iterations = LottieConstants.IterateForever,
        speed = 2f // double speed
    )

    Spacer(modifier = Modifier.height(height = 12.dp))

    Text(
        text = "Lottie Animation",
        fontSize = 28.sp
    )
}

Output:

Double Speed Animation Lottie

Let’s make it half.

@Composable
fun MyUI() {
    val composition by rememberLottieComposition(
        spec = LottieCompositionSpec.RawRes(resId = R.raw.running_man)
    )

    // render the animation
    LottieAnimation(
        modifier = Modifier.size(size = 240.dp),
        composition = composition,
        iterations = LottieConstants.IterateForever,
        speed = 0.5f // half of the normal speed
    )

    Spacer(modifier = Modifier.height(height = 12.dp))

    Text(
        text = "Lottie Animation",
        fontSize = 28.sp
    )
}

Output:

Half Speed Animation

Start the Animation on a Button Click:

LottieAnimation() takes isPlaying parameter. It is used to control whether the animation is playing or paused.

@Composable
fun MyUI() {
    val composition by rememberLottieComposition(
        spec = LottieCompositionSpec.RawRes(resId = R.raw.running_man)
    )

    var isPlaying by remember {
        mutableStateOf(false)
    }

    // render the animation
    LottieAnimation(
        modifier = Modifier.size(size = 240.dp),
        composition = composition,
        iterations = 1,
        isPlaying = isPlaying
    )

    Button(
        onClick = {
            isPlaying = true
        }
    ) {
        Text(text = "Start Animation")
    }
}

Output:

Animation on a Button Click

In the above output, when you tap on the button, the animation will play without any problem. However, if you tap the button again, nothing will happen. What if you want to restart the animation on every button click? We can achieve it with the help of animateLottieCompositionAsState(). It is used to track the progress of the animation.

@Composable
fun MyUI() {
    val composition by rememberLottieComposition(
        spec = LottieCompositionSpec.RawRes(resId = R.raw.running_man)
    )

    var isPlaying by remember {
        mutableStateOf(false)
    }

    val progress by animateLottieCompositionAsState(
        composition = composition,
        isPlaying = isPlaying,
        iterations = 1
    )

    // whenever progress is changed
    // this block is called
    LaunchedEffect(key1 = progress) {
        // start of animation
        if (progress == 0f) {
            isPlaying = true
        }

        // end of animation
        if (progress == 1f) {
            isPlaying = false
        }
    }

    // render the animation
    LottieAnimation(
        modifier = Modifier.size(size = 240.dp),
        composition = composition,
        progress = { progress }
    )

    Button(
        onClick = {
            isPlaying = true
        }
    ) {
        Text(text = "Start Animation")
    }
}

Output:

Lottie Button Click Animation

We are using another overload of LottieAnimation() that takes the progress parameter. As I mentioned earlier, 0f indicates the beginning of the animation, while the value 1f indicates the end. We are checking these conditions inside the LaunchedEffect block. The block gets called in two cases:

  1. When we launch the app for the first time.
  2. Whenever the progress value is changed.

In the button onClick block, we are setting isPlaying = true. When we tap on the button, isPlaying becomes true, and animateLottieCompositionAsState() resets the progress to 0f. As a result, the animation will play. When the progress becomes 1f, isPlaying becomes false (inside the LaunchedEffect), and the animation will stop.

The problem with the above code is that the animation will play as soon as you launch the app. This is because progress is initially 0f, and the LaunchedEffect block is called when we open the app for the first time. As a result, isPlaying becomes true inside the block. We can fix this with the help of a simple variable.

@Composable
fun MyUI() {
    val composition by rememberLottieComposition(
        spec = LottieCompositionSpec.RawRes(resId = R.raw.running_man)
    )

    var isPlaying by remember {
        mutableStateOf(false) // initially no animation
    }

    val progress by animateLottieCompositionAsState(
        composition = composition,
        isPlaying = isPlaying,
        iterations = 1
    )

    var appLaunchedFirstTime by remember {
        mutableStateOf(true)
    }

    // whenever progress is changed
    // this block is called
    LaunchedEffect(key1 = progress) {
        if (!appLaunchedFirstTime) {
            // start of animation
            if (progress == 0f) {
                isPlaying = true
            }

            // end of animation
            if (progress == 1f) {
                isPlaying = false
            }
        }
    }

    // render the animation
    LottieAnimation(
        modifier = Modifier.size(size = 240.dp),
        composition = composition,
        progress = { progress }
    )

    Button(
        onClick = {
            appLaunchedFirstTime = false
            isPlaying = true
        }
    ) {
        Text(text = "Start Animation")
    }
}

If you run the code, the animation won’t play before the button click.

Stop Lottie Animation and Show the Task Completed Message:

Most of the time, we want to display a message after the animation. We can achieve it using the following code:

@Composable
fun MyUI() {
    val composition by rememberLottieComposition(
        spec = LottieCompositionSpec.RawRes(resId = R.raw.running_man)
    )

    var isTaskCompleted by remember {
        mutableStateOf(false)
    }

    // this is called when the app is launched first time
    LaunchedEffect(key1 = Unit) {
        delay(3000)
        isTaskCompleted = true
    }

    if (isTaskCompleted) {
        Text(
            text = "Task Completed Successfully!",
            fontSize = 20.sp
        )
    }
    else {
        // render the animation
        LottieAnimation(
            modifier = Modifier.size(size = 240.dp),
            composition = composition,
            iterations = LottieConstants.IterateForever // animate forever
        )

        Text(
            text = "Lottie Animation",
            fontSize = 28.sp
        )
    }
}

Output:

success message after animation

We created a isTaskCompleted variable. In the LaunchedEffect, we added a 3-second delay and set the isTaskCompleted = true. Initially, isTaskCompleted is false, so the animation will play. After 3 seconds, the variable becomes true, and the success message is shown.

This is all about Lottie animations in Jetpack Compose. I hope you have learned something new. If you have any doubts, leave a comment below.

Related Articles:


References: