{% setvar book_path %}/reference/androidx/_book.yaml{% endsetvar %} {% include "_shared/_reference-head-tags.html" %}

Animatable

public final class Animatable<T extends Object, V extends AnimationVector>


Animatable is a value holder that automatically animates its value when the value is changed via animateTo. If animateTo is invoked during an ongoing value change animation, a new animation will transition Animatable from its current value (i.e. value at the point of interruption) to the new targetValue. This ensures that the value change is always continuous using animateTo. If a spring animation (e.g. default animation) is used with animateTo, the velocity change will guarantee to be continuous as well.

Unlike AnimationState, Animatable ensures mutual exclusiveness on its animations. To achieve this, when a new animation is started via animateTo (or animateDecay), any ongoing animation will be canceled via a CancellationException.

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Text
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp

// Creates an `Animatable` to animate Offset and `remember` it.
val animatedOffset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) }

Box(
    Modifier.fillMaxSize().background(Color(0xffb99aff)).pointerInput(Unit) {
        coroutineScope {
            while (true) {
                val offset = awaitPointerEventScope {
                    awaitFirstDown().position
                }
                // Launch a new coroutine for animation so the touch detection thread is not
                // blocked.
                launch {
                    // Animates to the pressed position, with the given animation spec.
                    animatedOffset.animateTo(
                        offset,
                        animationSpec = spring(stiffness = Spring.StiffnessLow)
                    )
                }
            }
        }
    }
) {
    Text("Tap anywhere", Modifier.align(Alignment.Center))
    Box(
        Modifier
            .offset {
                // Use the animated offset as the offset of the Box.
                IntOffset(
                    animatedOffset.value.x.roundToInt(),
                    animatedOffset.value.y.roundToInt()
                )
            }
            .size(40.dp)
            .background(Color(0xff3c1361), CircleShape)
    )
}
See also
animateTo
animateDecay

Summary

Public constructors

<T extends Object, V extends AnimationVector> Animatable(
    @NonNull T initialValue,
    @NonNull TwoWayConverter<@NonNull T, @NonNull V> typeConverter,
    T visibilityThreshold,
    @NonNull String label
)

Public methods

final @NonNull AnimationResult<@NonNull T, @NonNull V>
animateDecay(
    @NonNull T initialVelocity,
    @NonNull DecayAnimationSpec<@NonNull T> animationSpec,
    @ExtensionFunctionType Function1<@NonNull Animatable<@NonNull T, @NonNull V>, Unit> block
)

Start a decay animation (i.e. an animation that slows down from the given initialVelocity starting at current Animatable.value until the velocity reaches 0.

final @NonNull AnimationResult<@NonNull T, @NonNull V>
animateTo(
    @NonNull T targetValue,
    @NonNull AnimationSpec<@NonNull T> animationSpec,
    @NonNull T initialVelocity,
    @ExtensionFunctionType Function1<@NonNull Animatable<@NonNull T, @NonNull V>, Unit> block
)

Starts an animation to animate from value to the provided targetValue.

final @NonNull State<@NonNull T>

Returns a State representing the current value of this animation.

final @NonNull String
final T

Lower bound of the animation, null by default (meaning no lower bound).

final @NonNull T

The target of the current animation.

final @NonNull TwoWayConverter<@NonNull T, @NonNull V>

A two-way converter that converts the given type T from and to AnimationVector

final T

Upper bound of the animation, null by default (meaning no upper bound).

final @NonNull T

Current value of the animation.

final @NonNull T

Returns the velocity, converted from velocityVector.

final @NonNull V

Velocity vector of the animation (in the form of AnimationVector.

final boolean

Indicates whether the animation is running.

final void
snapTo(@NonNull T targetValue)

Sets the current value to the target value, without any animation.

final void

Stops any on-going animation with a CancellationException.

final void
updateBounds(T lowerBound, T upperBound)

Updates either lowerBound or upperBound, or both.

Public constructors

Animatable

public <T extends Object, V extends AnimationVector> Animatable(
    @NonNull T initialValue,
    @NonNull TwoWayConverter<@NonNull T, @NonNull V> typeConverter,
    T visibilityThreshold,
    @NonNull String label
)
Parameters
@NonNull T initialValue

initial value of the animatable value holder

@NonNull TwoWayConverter<@NonNull T, @NonNull V> typeConverter

A two-way converter that converts the given type T from and to AnimationVector

T visibilityThreshold

Threshold at which the animation may round off to its target value.

Public methods

animateDecay

public final @NonNull AnimationResult<@NonNull T, @NonNull V> animateDecay(
    @NonNull T initialVelocity,
    @NonNull DecayAnimationSpec<@NonNull T> animationSpec,
    @ExtensionFunctionType Function1<@NonNull Animatable<@NonNull T, @NonNull V>, Unit> block
)

Start a decay animation (i.e. an animation that slows down from the given initialVelocity starting at current Animatable.value until the velocity reaches 0. If there's already an ongoing animation, the animation in-flight will be immediately cancelled. Decay animation is often used after a fling gesture.

animationSpec defines the decay animation that will be used for this animation. Some options for this animationSpec include: splineBasedDecay and exponentialDecay. block will be invoked on each animation frame.

Returns an AnimationResult object, that contains the reason for ending the animation, and an end state of the animation. The reason for ending the animation will be Finished if the animation finishes successfully without any interruption. If the animation reaches the either lowerBound or upperBound in any dimension, the animation will end with BoundReached being the end reason.

If the animation gets interrupted by 1) another call to start an animation (i.e. animateTo/animateDecay), 2) Animatable.stop, or 3)Animatable.snapTo, the canceled animation will throw a CancellationException as the job gets canceled. As a result, all the subsequent work in the caller's coroutine will be canceled. This is often the desired behavior. If there's any cleanup that needs to be done when an animation gets canceled, consider starting the animation in a try-catch block.

Note, once the animation ends, its velocity will be reset to 0. If there's a need to continue the momentum before the animation gets interrupted or reaches the bound, it's recommended to use the velocity in the returned AnimationResult.endState to start another animation.

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.calculateTargetValue
import androidx.compose.animation.core.spring
import androidx.compose.animation.splineBasedDecay
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.verticalDrag
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.input.pointer.util.VelocityTracker
import androidx.compose.ui.unit.IntOffset

/**
 * In this example, we create a swipe-to-dismiss modifier that dismisses the child via a
 * vertical swipe-up.
 */
fun Modifier.swipeToDismiss(): Modifier = composed {
    // Creates a Float type `Animatable` and `remember`s it
    val animatedOffsetY = remember { Animatable(0f) }
    this.pointerInput(Unit) {
        coroutineScope {
            while (true) {
                val pointerId = awaitPointerEventScope {
                    awaitFirstDown().id
                }
                val velocityTracker = VelocityTracker()
                awaitPointerEventScope {
                    verticalDrag(pointerId) {
                        // Snaps the value by the amount of finger movement
                        launch {
                            animatedOffsetY.snapTo(
                                animatedOffsetY.value + it.positionChange().y
                            )
                        }
                        velocityTracker.addPosition(
                            it.uptimeMillis,
                            it.position
                        )
                    }
                }
                // At this point, drag has finished. Now we obtain the velocity at the end of
                // the drag, and animate the offset with it as the starting velocity.
                val velocity = velocityTracker.calculateVelocity().y

                // The goal for the animation below is to animate the dismissal if the fling
                // velocity is high enough. Otherwise, spring back.
                launch {
                    // Checks where the animation will end using decay
                    val decay = splineBasedDecay<Float>(this@pointerInput)

                    // If the animation can naturally end outside of visual bounds, we will
                    // animate with decay.
                    if (decay.calculateTargetValue(
                            animatedOffsetY.value,
                            velocity
                        ) < -size.height
                    ) {
                        // (Optionally) updates lower bounds. This stops the animation as soon
                        // as bounds are reached.
                        animatedOffsetY.updateBounds(
                            lowerBound = -size.height.toFloat()
                        )
                        // Animate with the decay animation spec using the fling velocity
                        animatedOffsetY.animateDecay(velocity, decay)
                    } else {
                        // Not enough velocity to be dismissed, spring back to 0f
                        animatedOffsetY.animateTo(0f, initialVelocity = velocity)
                    }
                }
            }
        }
    }.offset { IntOffset(0, animatedOffsetY.value.roundToInt()) }
}

animateTo

public final @NonNull AnimationResult<@NonNull T, @NonNull V> animateTo(
    @NonNull T targetValue,
    @NonNull AnimationSpec<@NonNull T> animationSpec,
    @NonNull T initialVelocity,
    @ExtensionFunctionType Function1<@NonNull Animatable<@NonNull T, @NonNull V>, Unit> block
)

Starts an animation to animate from value to the provided targetValue. If there is already an animation in-flight, this method will cancel the ongoing animation before starting a new animation continuing the current value and velocity. It's recommended to set the optional initialVelocity only when animateTo is used immediately after a fling. In most of the other cases, altering velocity would result in visual discontinuity.

The animation will use the provided animationSpec to animate the value towards the targetValue. When no animationSpec is specified, a spring will be used. block will be invoked on each animation frame.

Returns an AnimationResult object. It contains: 1) the reason for ending the animation, and 2) an end state of the animation. The reason for ending the animation can be either of the following two:

If the animation gets interrupted by 1) another call to start an animation (i.e. animateTo/animateDecay), 2) Animatable.stop, or 3)Animatable.snapTo, the canceled animation will throw a CancellationException as the job gets canceled. As a result, all the subsequent work in the caller's coroutine will be canceled. This is often the desired behavior. If there's any cleanup that needs to be done when an animation gets canceled, consider starting the animation in a try-catch block.

Note: once the animation ends, its velocity will be reset to 0. The animation state at the point of interruption/reaching bound is captured in the returned AnimationResult. If there's a need to continue the momentum that the animation had before it was interrupted or reached the bound, it's recommended to use the velocity in the returned AnimationResult.endState to start another animation.

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.graphicsLayer

fun Modifier.fadeIn(): Modifier = composed {
    // Creates an `Animatable` and remembers it.
    val alphaAnimation = remember { Animatable(0f) }
    // Launches a coroutine for the animation when entering the composition.
    // Uses `alphaAnimation` as the subject so the job in `LaunchedEffect` will run only when
    // `alphaAnimation` is created, which happens one time when the modifier enters
    // composition.
    LaunchedEffect(alphaAnimation) {
        // Animates to 1f from 0f for the fade-in, and uses a 500ms tween animation.
        alphaAnimation.animateTo(
            targetValue = 1f,
            // Default animationSpec uses [spring] animation, here we overwrite the default.
            animationSpec = tween(500)
        )
    }
    this.graphicsLayer(alpha = alphaAnimation.value)
}

asState

public final @NonNull State<@NonNull T> asState()

Returns a State representing the current value of this animation. This allows hoisting the animation's current value without causing unnecessary recompositions when the value changes.

getLabel

public final @NonNull String getLabel()

getLowerBound

public final T getLowerBound()

Lower bound of the animation, null by default (meaning no lower bound). Bounds can be changed using updateBounds.

Animation will stop as soon as any dimension specified in lowerBound is reached. For example: For an Animatable with an lowerBound set to Offset(100f, 200f), when the value.x drops below 100f or value.y drops below 200f, the animation will stop.

getTargetValue

public final @NonNullgetTargetValue()

The target of the current animation. If the animation finishes un-interrupted, it will reach this target value.

getTypeConverter

public final @NonNull TwoWayConverter<@NonNull T, @NonNull V> getTypeConverter()

A two-way converter that converts the given type T from and to AnimationVector

getUpperBound

public final T getUpperBound()

Upper bound of the animation, null by default (meaning no upper bound). Bounds can be changed using updateBounds.

Animation will stop as soon as any dimension specified in upperBound is reached. For example: For an Animatable with an upperBound set to Offset(100f, 200f), when the value.x exceeds 100f or value.y exceeds 200f, the animation will stop.

getValue

public final @NonNullgetValue()

Current value of the animation.

getVelocity

public final @NonNullgetVelocity()

Returns the velocity, converted from velocityVector.

getVelocityVector

public final @NonNullgetVelocityVector()

Velocity vector of the animation (in the form of AnimationVector.

isRunning

public final boolean isRunning()

Indicates whether the animation is running.

snapTo

public final void snapTo(@NonNull T targetValue)

Sets the current value to the target value, without any animation. This will also cancel any on-going animation with a CancellationException. This function will return after canceling any on-going animation and updating the Animatable.value and Animatable.targetValue to the provided targetValue.

Note: If the lowerBound or upperBound is specified, the provided targetValue will be clamped to the bounds to ensure Animatable.value is always within bounds.

See animateTo and animateDecay for more details about animation being canceled.

Parameters
@NonNull T targetValue

The new target value to set value to.

stop

public final void stop()

Stops any on-going animation with a CancellationException.

This function will not return until the ongoing animation has been canceled (if any). Note, stop function does not skip the animation value to its target value. Rather the animation will be stopped in its track. Consider snapTo if it's desired to not only stop the animation but also snap the value to a given value.

See animateTo and animateDecay for more details about animation being canceled.

updateBounds

public final void updateBounds(T lowerBound, T upperBound)

Updates either lowerBound or upperBound, or both. This will update Animatable.lowerBound and/or Animatable.upperBound accordingly after a check to ensure the provided lowerBound is no greater than upperBound in any dimension.

Setting the bounds will immediate clamp the value, only if the animation isn't running. For the on-going animation, the value at the next frame update will be checked against the bounds. If the value reaches the bound, then the animation will end with BoundReached end reason.

Parameters
T lowerBound

lower bound of the animation. Defaults to the Animatable.lowerBound that is currently set.

T upperBound

upper bound of the animation. Defaults to the Animatable.upperBound that is currently set.

Throws
kotlin.IllegalStateException

if the lowerBound is greater than upperBound in any dimension.