Mastering Custom Animation Transitions in Jetpack Compose

Jetpack Compose provides a powerful and flexible system for creating animations within your Android applications. One of the most engaging ways to enhance user experience is by using custom animation transitions. These transitions define how UI elements change and move between different states, adding a layer of polish and interactivity to your app.

What are Custom Animation Transitions?

Custom animation transitions are animations that define how composables enter, exit, or change within the UI. Instead of using simple fades or slides, you can craft complex and tailored animations to fit the specific needs of your application.

Why Use Custom Animation Transitions?

  • Enhanced User Experience: Smooth transitions make the app feel more polished and professional.
  • Improved Interactivity: Provides visual feedback that the UI is responding to user input.
  • Branding and Style: Unique transitions can help define the look and feel of your application.

How to Implement Custom Animation Transitions in Jetpack Compose

Jetpack Compose provides several APIs for creating custom animation transitions, including AnimatedVisibility, Transition, and animate*AsState.

Method 1: Using AnimatedVisibility for Simple Transitions

AnimatedVisibility is the easiest way to animate the appearance and disappearance of composables.

Step 1: Basic Implementation

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

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

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

        AnimatedVisibility(
            visible = isVisible,
            enter = fadeIn(),
            exit = fadeOut()
        ) {
            Text("This text is animated.")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun AnimatedVisibilityExamplePreview() {
    AnimatedVisibilityExample()
}

In this example:

  • AnimatedVisibility animates the visibility of the Text composable based on the isVisible state.
  • fadeIn() and fadeOut() are used as enter and exit animations, respectively.
Step 2: Custom Enter/Exit Transitions

You can customize the enter and exit transitions using slideInVertically, slideOutVertically, scaleIn, and more.


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

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

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

        AnimatedVisibility(
            visible = isVisible,
            enter = slideInVertically(initialOffsetY = { -it }),
            exit = slideOutVertically(targetOffsetY = { -it })
        ) {
            Text("This text slides in and out.")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun CustomAnimatedVisibilityExamplePreview() {
    CustomAnimatedVisibilityExample()
}

Here, the Text composable slides in from the top and slides out to the top when toggled.

Method 2: Using Transition for Complex State Transitions

Transition is ideal for animating between multiple states with more control over each animation phase.

Step 1: Define States

Start by defining the states you want to animate between.


enum class BoxState {
    Collapsed,
    Expanded
}
Step 2: Create a Transition

Use updateTransition to create a transition that manages the animation between states.


import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun TransitionExample() {
    var currentState by remember { mutableStateOf(BoxState.Collapsed) }
    val transition = updateTransition(currentState, label = "box_state")

    val boxHeight by transition.animateDp(
        transitionSpec = {
            tween(durationMillis = 500)
        },
        label = "box_height"
    ) { state ->
        when (state) {
            BoxState.Collapsed -> 50.dp
            BoxState.Expanded -> 200.dp
        }
    }

    val textColor by transition.animateColor(
        transitionSpec = {
            tween(durationMillis = 500)
        },
        label = "text_color"
    ) { state ->
        when (state) {
            BoxState.Collapsed -> Color.White
            BoxState.Expanded -> Color.Black
        }
    }

    Box(
        modifier = Modifier
            .width(200.dp)
            .height(boxHeight)
            .background(Color.Blue)
            .clickable {
                currentState = if (currentState == BoxState.Collapsed) BoxState.Expanded else BoxState.Collapsed
            },
        contentAlignment = Alignment.Center
    ) {
        Text(text = "Animated Box", color = textColor)
    }
}

@Preview(showBackground = true)
@Composable
fun TransitionExamplePreview() {
    TransitionExample()
}

In this example:

  • updateTransition creates a transition between BoxState.Collapsed and BoxState.Expanded.
  • animateDp animates the height of the box, and animateColor animates the color of the text based on the current state.
  • transitionSpec defines the animation duration and easing function.

Method 3: Using animate*AsState for Single Value Animations

animate*AsState (e.g., animateFloatAsState, animateColorAsState) is useful for animating a single property between different values.


import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun AnimateAsStateExample() {
    var isGreen by remember { mutableStateOf(false) }
    val animatedColor by animateColorAsState(
        targetValue = if (isGreen) Color.Green else Color.Red,
        animationSpec = tween(durationMillis = 500, easing = FastOutLinearInEasing),
        label = "colorAnimation"
    )

    Box(
        modifier = Modifier
            .size(100.dp)
            .background(animatedColor)
            .clickable { isGreen = !isGreen },
        contentAlignment = Alignment.Center
    ) {
        Text(text = "Tap to Change", color = Color.White)
    }
}

@Preview(showBackground = true)
@Composable
fun AnimateAsStateExamplePreview() {
    AnimateAsStateExample()
}

Here, animateColorAsState animates the background color between red and green when the box is tapped.

Advanced Customizations

To create truly unique animations, consider the following advanced techniques:

  • Custom Easing Functions: Create custom easing functions to define the speed curve of your animations.
  • Keyframes: Use keyframes within transitionSpec to define multiple stages within a single animation.
  • AnimationVector: Leverage AnimationVector for fine-grained control over animating numerical properties.

Conclusion

Custom animation transitions are a vital tool for creating engaging and visually appealing Android applications with Jetpack Compose. Whether using AnimatedVisibility for simple transitions or Transition for complex state changes, mastering these techniques will elevate the user experience of your app. Experiment with different animations, easing functions, and timing to craft transitions that align with your application’s unique brand and style.