How to Animate Visibility in Jetpack Compose?

jetpack compose animate visibility

Today, we will learn how to animate the visibility of elements in Jetpack Compose using the AnimatedVisibility composable.

Prerequisites:

Animate Visibility in Jetpack Compose:

Jetpack Compose provides AnimatedVisibility API. It animates the appearance and disappearance of its content.

For this article, create a MyUI() composable in the MainActivity, and call it from the onCreate() method.

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.*
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp

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(),
                        verticalArrangement = Arrangement.Center,
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {
                        MyUI()
                    }
                }
            }
        }
    }
}

@Composable
private fun MyUI() {
   
}

We will write our code in the MyUI(). For the dog image, download it from pixabay and change its name to dog.jpg. Put it in the res > drawable folder.

Let’s understand the AnimatedVisibility with an example. Look at the following code.

@Composable
private fun MyUI() {

    var visible by remember {
        mutableStateOf(true)
    }

    if (visible) {
        Image(
            modifier = Modifier
                .size(size = 120.dp)
                .clip(shape = CircleShape),
            painter = painterResource(id = R.drawable.dog),
            contentDescription = "Dog",
            contentScale = ContentScale.Crop
        )
    }

    Button(
        modifier = Modifier.padding(top = 8.dp),
        onClick = {
            visible = !visible
        }
    ) {
        Text(text = "Toggle Visibility")
    }
}

Output:

Jetpack Compose visibility animation

The dog appears and disappears without any animation. To animate its visibility, replace if with AnimatedVisibility.

AnimatedVisibility (visible) {
    Image(
        modifier = Modifier
            .size(size = 120.dp)
            .clip(shape = CircleShape),
        painter = painterResource(id = R.drawable.dog),
        contentDescription = "Dog",
        contentScale = ContentScale.Crop
    )
}

Output:

jetpack compose animation

Note: In the older versions of Jetpack Compose, the default animation was different. So, you may see a different output.

AnimatedVisibility:

Let’s look at AnimatedVisibility composable:

@Composable
fun AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandIn(),
    exit: ExitTransition = shrinkOut() + fadeOut(),
    label: String = "AnimatedVisibility",
    content: @Composable() AnimatedVisibilityScope.() -> Unit
)

visible – Whether the content should be visible or not.

modifierThe modifier for the layout.

enter – Enter transition.

exit – Exit transition.

label – Name of the animation. It is used to identify the animation in the Android Studio pane.

content – Content to appear or disappear based on the value of visible.

Let’s change the enter and exit transitions.

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) {
    Image(
        modifier = Modifier
            .size(size = 120.dp)
            .clip(shape = CircleShape),
        painter = painterResource(id = R.drawable.dog),
        contentDescription = "Dog",
        contentScale = ContentScale.Crop
    )
}

Output:

Fade in out

We can combine transitions using the + operator.

AnimatedVisibility(
    visible = visible,
    enter = fadeIn() + slideInHorizontally(),
    exit = fadeOut() + slideOutHorizontally()
) {
    Image(
        modifier = Modifier
            .size(size = 120.dp)
            .clip(shape = CircleShape),
        painter = painterResource(id = R.drawable.dog),
        contentDescription = "Dog",
        contentScale = ContentScale.Crop
    )
}

Output:

Fade in out slide in out

Jetpack Compose offers 4 types of enter and exit transitions: Fade, Expand/Shrink, Scale and Slide. Look at the examples on the official site.

Animate Visibility of Child Components:

We can use the animateEnterExit() modifier to specify the different animation behavior for each child (direct or indirect) of AnimatedVisibility. The modifier is currently experimental.

In the above code, let’s add the dog’s name below the image.

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) {
    Column(
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(
            modifier = Modifier
                .size(size = 120.dp)
                .clip(shape = CircleShape)
                .animateEnterExit(
                    enter = expandVertically(),
                    exit = shrinkHorizontally()
                ),
            painter = painterResource(id = R.drawable.dog),
            contentDescription = "Dog",
            contentScale = ContentScale.Crop
        )

        Text(
            modifier = Modifier
                .padding(top = 8.dp)
                .animateEnterExit(
                    enter = slideInHorizontally(),
                    exit = slideOutHorizontally()
                ),
            text = "Frankie"
        )
    }
}

Output:

animate child components

Related: Text in Jetpack Compose (with Examples)

Note that the fadeIn() and fadeOut() transitions are also applied to children. This is because the final animation of the child component is the combination of the animations specified at the AnimatedVisibility composable and its own animations.

If you don’t like it, specify EnterTransition.None and ExitTransition.None at the AnimatedVisibility composable. As a result, the child components have their own distinct animations.

How to Observe Animation State?

There is another overload of AnimatedVisibility with the MutableTransitionState parameter.

@Composable
fun AnimatedVisibility(
    visibleState: MutableTransitionState<Boolean>,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandIn(),
    exit: ExitTransition = fadeOut() + shrinkOut(),
    label: String = "AnimatedVisibility",
    content: @Composable() AnimatedVisibilityScope.() -> Unit
)

It helps us to observe the animation state. We can also use it to trigger an animation as soon as the AnimatedVisibility is added to the composition tree.

@Composable
private fun MyUI() {

    val visibleState = remember {
        MutableTransitionState(false).apply {
            targetState = true // start the animation immediately
        }
    }

    AnimatedVisibility(
        visibleState = visibleState,
        enter = fadeIn() + slideInHorizontally(),
        exit = fadeOut() + slideOutHorizontally()
    ) {
        Image(
            modifier = Modifier
                .size(size = 120.dp)
                .clip(shape = CircleShape),
            painter = painterResource(id = R.drawable.dog),
            contentDescription = "Dog",
            contentScale = ContentScale.Crop
        )
    }

    Button(
        modifier = Modifier.padding(top = 8.dp),
        onClick = {
            visibleState.targetState = !visibleState.targetState
        }
    ) {
        Text(text = "Toggle Visibility")
    }

    // use the MutableTransitionState to know the current animation state
    Text(
        text = when {
            visibleState.isIdle && visibleState.currentState -> "Visible" // enter transition is completed
            !visibleState.isIdle && visibleState.currentState -> "Disappearing" // exit transition is running
            visibleState.isIdle && !visibleState.currentState -> "Invisible" // exit transition is completed
            else -> "Appearing" // enter transition is running
        }
    )
}

Output:

animation state

Jetpack Compose Custom Visibility Animation:

We can create custom enter/exit animations with the help of the Transition object from the AnimatedVisibilityScope.

Example:

@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun MyUI() {
    var visible by remember {
        mutableStateOf(true)
    }

    AnimatedVisibility(
        visible = visible,
        enter = fadeIn(),
        exit = fadeOut()
    ) {
        // this: AnimatedVisibilityScope
        // Use AnimatedVisibilityScope#transition to add a custom animation

        val backgroundColor by transition.animateColor(label = "ColorAnimation") { enterExistState ->
            if (enterExistState == EnterExitState.Visible) Color.Green else Color.Yellow
        }

        Box(
            modifier = Modifier
                .size(size = 120.dp)
                .background(color = backgroundColor)
        )
    }

    Button(
        modifier = Modifier.padding(top = 8.dp),
        onClick = {
            visible = !visible
        }
    ) {
        Text(text = "Toggle Visibility")
    }
}

Output:

animate color

Note:

1. All the animations added via transition property will run simultaneously with the enter and exit animations of AnimatedVisibility.

2. AnimatedVisibility waits until all the animations in the Transition have finished before removing its content. But, for exit animations that are not created using the transition property (such as animateFloatAsState), AnimatedVisibility would not wait and may remove the content before they finish.

In the above code, EnterExitState is an enum class. It contains three states – PreEnter, Visible, and PostExit.

PreEnter – It is the initial state of the custom enter animation.

Visible – It is the last state of the custom enter animation and also the initial state of the custom exit animation.

PostExit – It is the last state of the custom exit animation.

See updateTransition to learn more about Transition.

Things to Remember About AnimatedVisibility:

1. The ColumnScope.AnimatedVisibility composable animates its content when the AnimatedVisibility is in a Column. The default animations are tailored specific to the Column layout. For Row, use RowScope.AnimatedVisibility.

2. AnimatedVisibility creates a custom layout for its content. The size of the custom layout is determined by the largest width and largest height of the children. All children will be aligned to the top start of the Layout.

3. When you combine animations using the + operator, the order of the combination does not matter, as the transition animations will start simultaneously.

This is all about how to animate the visibility in Android Jetpack Compose. I hope you have learned something new. If you have any doubts, comment below.

Related Posts:

References:

2 thoughts on “How to Animate Visibility in Jetpack Compose?”

Leave a Comment