Blog Infos
Author
Published
Topics
, , ,
Published

Image creadet with ChatGpt

“Jetpack Compose makes animations not only possible, but delightful — using state-driven, declarative APIs that feel natural and clean.”

Understanding animate*AsState in Jetpack Compose

Before we dive into specific animation examples, it’s worth getting familiar with the powerful and beginner-friendly animate*AsState APIs — the foundation of most state-based animations in Jetpack Compose.

What is animate*AsState?

Jetpack Compose’s animate*AsState functions help you animate values when state changes. Instead of instantly jumping from one value to another, Compose animates the change smoothly.

There are several built-in versions:

  • animateDpAsState() – for sizes and dimensions
  • animateFloatAsState() – for rotation, alpha, progress, etc.
  • animateColorAsState() – for animating color changes
  • animateIntAsState()animateOffsetAsState(), etc.

They all share the same structure.

Why it’s great:
  • Simple and declarative: Just swap state, and the animation happens.
  • Reusable: Works inside any layout or modifier (like size()rotate(), or padding()).
  • Powerful: Supports different animation specs (tweenspringkeyframes, etc.)

Now that we understand the magic behind animate*AsState, let’s explore how to use it in real-world UI animations like size transitions, visibility toggles, and interactive rotations. 👇

In this article, we’ll explore how to create smooth size transitions using one of the simplest but most powerful tools in Jetpack Compose: animateDpAsState,animateFloatAsState and AnimatedVisibility.

We’ll walk through the example line by line, explain the key concepts like rememberMutableState, and tween, and show how Compose handles animation with minimal effort.

1. Growing and Shrinking a Box

 

@Composable
fun SizeAnimationExample() {
    var isGrow by remember { mutableStateOf(false) }
    val boxSize by animateDpAsState(
        targetValue = if (isGrow) 200.dp else 100.dp,
        animationSpec = tween(durationMillis = 1000),
        label = ""
    )

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Button(onClick = { isGrow = !isGrow }) {
            Text(if (isGrow) "Shrink Box" else "Grow Box")
        }

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

        Box(
            modifier = Modifier
                .size(boxSize)
                .background(Color.Red)
        )
    }
}

 

Think of it like a switch

This animation works like a light switch:

  • When isGrow is false, the box is small (100.dp).
  • When you click the button and flip isGrow to true, the box smoothly grows to 200.dp.

So what’s actually happening under the hood? Let’s break it down line by line

Line 1: var isGrow by remember { mutableStateOf(false) }
  • var isGrow: This is a mutable Boolean state.
  • remember: Ensures that the value is retained across recompositions.
  • mutableStateOf(false): Creates observable state initialized as false.

When isGrow changes, Compose automatically triggers a recomposition — redrawing any Composables that depend on it.

Line 2: val boxSize by animateDpAsState(...)

This line is where the animation happens.

  • animateDpAsState: Animates a Dp value over time when its target changes.
  • targetValue: If isGrow is true → animate to 200.dp, otherwise → 100.dp.
  • tween(durationMillis = 1000): Defines a 1-second smooth animation.
  • boxSize becomes a smoothly animated value between those two endpoints.

As isGrow changes, boxSize changes gradually — and because it’s used inside Modifier.size(), the box resizes smoothly.

How the UI responds

Let’s walk through the flow:

  1. On launch, isGrow = false → box starts at 100.dp.
  2. You click the button → isGrow = true.
  3. animateDpAsState animates from 100.dp to 200.dp.
  4. The Box modifier updates with the new animated size in real time.
  5. Click again → animation reverses.

The result is a polished, native-feeling transition — with no manual animation code, handlers, or frame calculations.

Fading Elements In and Out with AnimatedVisibility

Jetpack Compose offers a very expressive way to show or hide content smoothly using the AnimatedVisibility composable.

What is AnimatedVisibility?

Think of it like a magic container:
When a Boolean value (e.g., isVisible) changes, it handles both the appearance and disappearance of its content with animation.

2. Fade a Box In and Out

 

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

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Button(onClick = { isVisible = !isVisible }) {
            Text(if (isVisible) "Hide" else "Show")
        }

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

        AnimatedVisibility(
            visible = isVisible,
            enter = fadeIn(animationSpec = tween(durationMillis = 1000)),
            exit = fadeOut(animationSpec = tween(durationMillis = 1000))
        ) {
            Box(
                modifier = Modifier
                    .size(100.dp)
                    .background(Color.Blue)
            )
        }
    }
}

 

How It Works
1. visible = isVisible
  • This controls whether the content should be visible.
  • isVisible is a MutableState value that determines the visibility.
  • When this value changes, the UI recomposes and the proper animation is triggered.
2. enter = fadeIn(...)
  • Defines how the content should appear.
  • fadeIn() is a built-in transition that starts at full transparency and fades to visible.
  • tween(durationMillis = 1000) makes the fade-in last for 1 second.

When isVisible becomes true, the content fades in.

3. exit = fadeOut(...)
  • Defines how the content should disappear.
  • fadeOut() gradually fades the content out until it’s fully transparent.
  • It also uses tween(durationMillis = 1000) to last 1 second.

When isVisible becomes false, the content fades out.

TL;DR — In Simple Terms
  • AnimatedVisibility = “Show/Hide with animation”
  • isVisible = “The switch that controls visibility”
  • fadeIn() = “Smooth entry”, and fadeOut() = “Smooth exit”
  • tween(1000) = “Take 1 second to animate”
Full Lifecycle
  1. Initial stateisVisible = true → content is shown.
  2. You click the button → isVisible = false.
  3. AnimatedVisibility detects the change:
  4. Triggers the exit animation (fadeOut)
  5. Fades the content out over 1 second
  6. Click again → isVisible = true:
  7. Triggers enter animation (fadeIn)
  8. Content fades back in

All of this happens automatically — no manual animation logic required.

Pro Tip

You’re not limited to just fading. You can combine or replace transitions with:

  • slideInVertically()slideOutHorizontally()
  • expandVertically()shrinkOut()
  • scaleIn()scaleOut()
3. Animating Rotation with animateFloatAsState

Sometimes you want to add a little motion to your interactions like rolling a dice or refreshing content. Here’s how you can do that with animateFloatAsState:

Setup

 

var isRolling by remember { mutableStateOf(false) }

val rotationAngle by animateFloatAsState(
    targetValue = if (isRolling) 360f else 0f,
    animationSpec = tween(durationMillis = 500),
    finishedListener = {
        isRolling = false
    }
)

 

Usage in a Button

IconButton(
    onClick = {
        if (!isRolling) {
            isRolling = true
            viewModel.fetchZenQuote()
        }
    },
    modifier = Modifier.rotate(rotationAngle),
) {
    Icon(
        imageVector = Icons.Default.Casino,
        contentDescription = "New quote",
        tint = Color.White,
    )
}

 

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

With the advent of Android 15, edge-to-edge design has become the default configuration. Consequently, applications must be capable of accommodating window insets, including the system status bar and navigation bar, as well as supporting drawing…
Watch Video

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

Timo Drick
Lead Android developer
Seven Principles Mobility GmbH

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

Timo Drick
Lead Android develop ...
Seven Principles Mob ...

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

Timo Drick
Lead Android developer
Seven Principles Mobility ...

Jobs

How It Works
  • isRolling starts as false.
  • When the user taps the button, it flips isRolling to true.
  • animateFloatAsState detects the change and animates rotationAngle from 0f → 360f.
  • Once the spin completes, finishedListener resets isRolling to false.

This creates a clean, polished 360° spin effect every time the button is pressed. It’s perfect for dice rolls, card shuffles, or random content refreshes.

Why This Works So Well in Compose

Jetpack Compose animations are:

  • Declarative — Just describe the end state.
  • State-driven — Powered by remember and MutableState.
  • Composable — Plug into any layout or modifier easily.

You don’t manage time or frames and Compose handles that internally. All you do is update state, and the animation system figures out how to transition the UI.

Follow me for more Android and Tech content!

Let’s connect on Linkedin!

This article was previously published on proandroiddev.com.

Menu