AnimatedVisibility Transitions in Jetpack Compose: A Comprehensive Guide

Jetpack Compose, Android’s modern UI toolkit, provides powerful and declarative APIs for building UIs. One of the most engaging aspects of modern UIs is the use of animations. Jetpack Compose offers a straightforward way to animate the visibility of composables using the AnimatedVisibility composable, complete with customizable transitions. This article explores how to use AnimatedVisibility to create captivating and smooth transitions in your Compose apps.

What is AnimatedVisibility in Jetpack Compose?

AnimatedVisibility is a composable provided by Jetpack Compose that allows you to animate the appearance and disappearance of UI elements. It extends beyond simple visibility toggling by enabling transitions that define how the content animates in or out.

Why Use AnimatedVisibility?

  • Improved User Experience: Smooth transitions enhance the perceived performance and polish of your application.
  • Engagement: Thoughtful animations capture the user’s attention and guide them through the UI.
  • Declarative API: Simple and expressive syntax integrates seamlessly with Jetpack Compose’s declarative nature.

How to Implement Animated Visibility Transitions

Implementing animated visibility in Jetpack Compose involves using the AnimatedVisibility composable along with transition specifications. Here’s a step-by-step guide:

Step 1: Add Necessary Dependencies

Ensure your project has the required dependencies. Add the following to your build.gradle.kts file:

dependencies {
    implementation("androidx.compose.animation:animation-core:1.6.0") // or newer
    implementation("androidx.compose.animation:animation:1.6.0") // or newer
    implementation("androidx.compose.ui:ui:1.6.0") // Ensure you are on the latest version
    implementation("androidx.compose.material:material:1.6.0")  // Add material dependencies

}

Step 2: Basic Usage of AnimatedVisibility

The simplest way to use AnimatedVisibility is to wrap the composable you want to animate. Here’s an example that fades content in and out:


import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun BasicAnimatedVisibility() {
    var isVisible by remember { mutableStateOf(true) }

    Column {
        Button(onClick = { isVisible = !isVisible }) {
            Text("Toggle Visibility")
        }

        AnimatedVisibility(
            visible = isVisible,
            enter = fadeIn(),
            exit = fadeOut()
        ) {
            Text("This text will fade in and out.")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun PreviewBasicAnimatedVisibility() {
    BasicAnimatedVisibility()
}

In this code:

  • isVisible is a state variable that tracks the visibility of the Text composable.
  • AnimatedVisibility wraps the Text composable.
  • enter = fadeIn() specifies that the composable should fade in when becoming visible.
  • exit = fadeOut() specifies that the composable should fade out when becoming invisible.

Step 3: Using Different Transition Effects

AnimatedVisibility provides a variety of built-in transitions. Let’s explore a few common ones.

Slide In/Out

To slide the composable in and out, you can use slideInVertically and slideOutVertically:


import androidx.compose.animation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.Alignment

@Composable
fun SlideAnimatedVisibility() {
    var isVisible by remember { mutableStateOf(true) }

    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Button(onClick = { isVisible = !isVisible }) {
            Text("Toggle Visibility")
        }

        AnimatedVisibility(
            visible = isVisible,
            enter = slideInVertically(
                initialOffsetY = { fullHeight -> -fullHeight }
            ),
            exit = slideOutVertically(
                targetOffsetY = { fullHeight -> -fullHeight }
            )
        ) {
            Text("This text will slide in and out from the top.")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun PreviewSlideAnimatedVisibility() {
    SlideAnimatedVisibility()
}

Here, initialOffsetY specifies the initial offset from which the composable slides in, and targetOffsetY specifies where it slides out to.

Expand/Shrink

You can use expandIn and shrinkOut to create an expanding/shrinking effect:


import androidx.compose.animation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun ExpandShrinkAnimatedVisibility() {
    var isVisible by remember { mutableStateOf(true) }

    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Button(onClick = { isVisible = !isVisible }) {
            Text("Toggle Visibility")
        }

        AnimatedVisibility(
            visible = isVisible,
            enter = expandIn(expandFrom = Alignment.Center),
            exit = shrinkOut(shrinkTowards = Alignment.Center)
        ) {
            Text("This text will expand and shrink.")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun PreviewExpandShrinkAnimatedVisibility() {
    ExpandShrinkAnimatedVisibility()
}

This example uses expandFrom and shrinkTowards to specify the origin from which the content expands and shrinks.

Combining Transitions

You can combine multiple transitions to create more complex effects. For example, combine fading and sliding:


import androidx.compose.animation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun CombinedAnimatedVisibility() {
    var isVisible by remember { mutableStateOf(true) }

    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Button(onClick = { isVisible = !isVisible }) {
            Text("Toggle Visibility")
        }

        AnimatedVisibility(
            visible = isVisible,
            enter = fadeIn() + slideInVertically(),
            exit = fadeOut() + slideOutVertically()
        ) {
            Text("This text will fade and slide.")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun PreviewCombinedAnimatedVisibility() {
    CombinedAnimatedVisibility()
}

Step 4: Customizing Transition Duration and Easing

You can customize the duration and easing of transitions using animationSpec. Here’s an example that customizes the fade-in effect:


import androidx.compose.animation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.animation.core.tween

@Composable
fun CustomDurationAnimatedVisibility() {
    var isVisible by remember { mutableStateOf(true) }

    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Button(onClick = { isVisible = !isVisible }) {
            Text("Toggle Visibility")
        }

        AnimatedVisibility(
            visible = isVisible,
            enter = fadeIn(animationSpec = tween(durationMillis = 1000)),
            exit = fadeOut(animationSpec = tween(durationMillis = 500))
        ) {
            Text("This text will fade in and out with custom durations.")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun PreviewCustomDurationAnimatedVisibility() {
    CustomDurationAnimatedVisibility()
}

In this code, tween(durationMillis = 1000) specifies a 1000ms duration for the fade-in effect. You can also use different easing curves to further customize the animation.

Best Practices

  • Use Meaningful Animations: Animations should enhance the user experience, not distract from it.
  • Keep Animations Short: Long animations can be frustrating. Aim for animations that are quick and responsive.
  • Test Performance: Ensure animations are smooth and don’t cause performance issues, especially on lower-end devices.

Conclusion

AnimatedVisibility in Jetpack Compose provides a powerful and flexible way to add engaging transitions to your Android UIs. By combining different transition effects and customizing their durations and easing, you can create a polished and intuitive user experience. Experiment with different animations to find the best fit for your application and delight your users with smooth and meaningful transitions.