animate*AsState

Most animations you'll write in Compose aren't complicated timelines — they're a single value that needs to glide from its old value to a new one whenever some piece of state changes. A boolean flips, and a color should ease from grey to red; a selection changes, and a size should grow. The animate*AsState family of functions — animateFloatAsState, animateColorAsState, animateDpAsState, and several more for other types — exists exactly for this: hand it a target value, and it returns a State that automatically animates toward that target on every recomposition where the target changed.

The Problem: Animating One Value By Hand

Without help, animating a single property means manually driving an Animatable inside a LaunchedEffect every time the target changes — tracking the old value, launching a coroutine, cancelling it if the target changes again mid-flight. animate*AsState hides all of that bookkeeping behind one function call:

Without animate*AsState (manual, more code than you need)

val tint = remember { Animatable(Color.Gray) }
LaunchedEffect(isFavorite) {
    tint.animateTo(if (isFavorite) Color.Red else Color.Gray)
}

With animateColorAsState (one line)

val tint by animateColorAsState(
    targetValue = if (isFavorite) Color.Red else Color.Gray,
    label = "favoriteTint"
)

Both approaches end up calling the same underlying animation machinery, but animateColorAsState handles interruption (retargeting mid-animation) and cleanup for you. Reach for a raw Animatable only when you need something animate*AsState doesn't offer, such as driving the animation from a gesture instead of from a state change — that's covered later in Gesture-Driven Animations.

animateFloatAsState, animateColorAsState & animateDpAsState

Compose ships a typed animate*AsState function for each common animatable type, so you never have to cast or convert:

FunctionAnimates
animateFloatAsStateA plain Float — scale factors, alpha, progress fractions
animateColorAsStateA Color — tints, backgrounds, borders
animateDpAsStateA Dp — sizes, padding, elevation
animateIntAsState, animateOffsetAsState, ...Ints, Offsets, Rects and other common types

Every one of them follows the same shape: pass a targetValue, get back a State<T> you read with by. Whenever the composable recomposes with a different targetValue, the returned state smoothly animates from wherever it currently is to the new target — even if the previous animation hadn't finished yet.

Controlling the Motion with animationSpec

Every animate*AsState call accepts an optional animationSpec parameter that controls the timing curve. The two you'll reach for most often are tween (a fixed-duration animation with an easing curve) and spring (a physics-based animation described by damping and stiffness, which naturally handles being interrupted mid-flight):

tween: fixed duration and easing

val alpha by animateFloatAsState(
    targetValue = if (isVisible) 1f else 0f,
    animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing),
    label = "alpha"
)

spring: physics-based, naturally interruptible

val scale by animateFloatAsState(
    targetValue = if (isPressed) 0.95f else 1f,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioMediumBouncy,
        stiffness = Spring.StiffnessLow
    ),
    label = "scale"
)

spring is generally the better default for anything that responds to user interaction (taps, drags, toggles) because a spring that's interrupted mid-motion re-targets smoothly from its current velocity, whereas a tween that gets retargeted restarts its easing curve from scratch, which can look like a small stutter.

Worked Example: RecipeBox's Favorite Icon

RecipeBox lets a user tap a heart icon on a recipe card to mark it as a favorite. Two things should change together when that happens: the icon's tint animates from grey to red, and the icon briefly scales up to give the tap some weight. Both are single-value animations, so both are perfect animate*AsState candidates:

RecipeBox: the animated favorite icon

@Composable
fun FavoriteIcon(
    isFavorite: Boolean,
    onToggle: () -> Unit,
    modifier: Modifier = Modifier
) {
    val tint by animateColorAsState(
        targetValue = if (isFavorite) Color(0xFFE53935) else Color(0xFF9E9E9E),
        animationSpec = tween(durationMillis = 250),
        label = "favoriteTint"
    )
    val scale by animateFloatAsState(
        targetValue = if (isFavorite) 1.2f else 1f,
        animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy),
        label = "favoriteScale"
    )

    Icon(
        imageVector = Icons.Filled.Favorite,
        contentDescription = "Toggle favorite",
        tint = tint,
        modifier = modifier
            .scale(scale)
            .clickable { onToggle() }
    )
}

Both tint and scale are ordinary Color and Float state values as far as the rest of FavoriteIcon is concerned — there's no manual coroutine, no cleanup, and tapping the heart repeatedly before an animation finishes just smoothly retargets both values instead of glitching.

Common Pitfalls

MistakeWhy it hurts
Forgetting the label parameterNot a compile error, but it makes the animation show up unnamed in the Android Studio animation inspector, which is painful to debug
Using animate*AsState for a value driven by a live drag gestureIt only animates toward a target on recomposition — a raw Animatable updated in pointerInput is the right tool there
Recomputing the target with an unstable expression every recompositionCauses the "animation" to restart every frame instead of running once toward a stable target
Next up: Animating a single value is one thing; animating whether a whole composable exists on screen at all is another. AnimatedVisibility covers that next.