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:

Jetpack Compose Animate Visibility:

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

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

@Composable
private fun MyUI() {

    var visible by remember {
        mutableStateOf(true)
    }

    Column(
        modifier = Modifier
            .fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {

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

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

If you want the dog’s image, you can get it from here.

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"
    )
}

Output:

jetpack compose animation

AnimatedVisibility:

Let’s look at AnimatedVisibility composable:

@Composable
fun ColumnScope.AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandVertically(),
    exit: ExitTransition = fadeOut() + shrinkVertically(),
    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"
    )
}

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"
    )
}

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"
        )

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

Output:

animate child components

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. 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
        }
    }

    Column(
        modifier = Modifier
            .fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {

        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"
            )
        }

        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
        // of the AnimatedVisibility
        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)
    }

    Column(
        modifier = Modifier
            .fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {

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

            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.

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.

Continue Exploring Jetpack Compose:

Leave a Comment