Mobile Development 10 min read

Understanding Property Animations in Jetpack Compose: animateColorAsState and Related APIs

This article explains how Jetpack Compose implements property animations by detailing the animateColorAsState function, its parameters, related animation helpers such as animateDpAsState and animateValueAsState, and the underlying Animatable, TargetBasedAnimation, and runAnimation mechanisms, complete with Kotlin code examples.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Understanding Property Animations in Jetpack Compose: animateColorAsState and Related APIs

Jetpack Compose provides a comprehensive set of APIs for property animations, allowing developers to animate values by smoothly transitioning between a start and an end state. This article focuses on the animateColorAsState function and explores the related animation helpers offered by Compose.

animateColorAsState API

The function signature is:

@Composable
fun animateColorAsState(
    targetValue: Color,
    animationSpec: AnimationSpec<Color> = colorDefaultSpring,
    label: String = "ColorAnimation",
    finishedListener: ((Color) -> Unit)? = null
): State<Color> {
    val converter = remember(targetValue.colorSpace) {
        (Color.VectorConverter)(targetValue.colorSpace)
    }
    return animateValueAsState(
        targetValue, converter, animationSpec, label = label, finishedListener = finishedListener
    )
}

It accepts four parameters: targetValue (required), animationSpec, label, and finishedListener. The function delegates the work to animateValueAsState after converting the color to an AnimationVector using a TwoWayConverter.

Other animation helpers

Compose also provides similar functions for other types, such as:

@Composable
fun animateDpAsState(
    targetValue: Dp,
    animationSpec: AnimationSpec<Dp> = dpDefaultSpring,
    label: String = "DpAnimation",
    finishedListener: ((Dp) -> Unit)? = null
): State<Dp> {
    return animateValueAsState(
        targetValue,
        Dp.VectorConverter,
        animationSpec,
        label = label,
        finishedListener = finishedListener
    )
}

@Composable
fun animateIntAsState(
    targetValue: Int,
    animationSpec: AnimationSpec<Int> = intDefaultSpring,
    label: String = "IntAnimation",
    finishedListener: ((Int) -> Unit)? = null
) { /* ... */ }

@Composable
fun animateSizeAsState(
    targetValue: Size,
    animationSpec: AnimationSpec<Size> = sizeDefaultSpring,
    label: String = "SizeAnimation",
    finishedListener: ((Size) -> Unit)? = null
) { /* ... */ }

@Composable
fun animateRectAsState(
    targetValue: Rect,
    animationSpec: AnimationSpec<Rect> = rectDefaultSpring,
    label: String = "RectAnimation",
    finishedListener: ((Rect) -> Unit)? = null
) { /* ... */ }

All these helpers ultimately call animateValueAsState, which is the core implementation handling generic type animation.

Core implementation: animateValueAsState

@Composable
fun <T, V : AnimationVector> animateValueAsState(
    targetValue: T,
    typeConverter: TwoWayConverter<T, V>,
    animationSpec: AnimationSpec<T> = remember { spring() },
    visibilityThreshold: T? = null,
    label: String = "ValueAnimation",
    finishedListener: ((T) -> Unit)? = null
): State<T> {
    // implementation details
}

The function creates an Animatable instance, remembers the listener, and sets up a Channel to receive target values. It then launches a coroutine that animates to new targets using animatable.animateTo and invokes the listener upon completion.

Animatable class

class Animatable<T, V : AnimationVector>(
    initialValue: T,
    val typeConverter: TwoWayConverter<T, V>,
    private val visibilityThreshold: T? = null,
    val label: String = "Animatable"
)

The animateTo method builds a TargetBasedAnimation with the provided spec and runs it via runAnimation, returning an AnimationResult that contains the final state and the reason the animation ended.

runAnimation and animation loop

private suspend fun runAnimation(
    animation: Animation<T, V>,
    initialVelocity: T,
    block: (Animatable<T, V>.() -> Unit)?
): AnimationResult<T, V> {
    val startTime = internalState.lastFrameTimeNanos
    return mutatorMutex.mutate {
        try {
            // animation loop updating internalState
            endState.animate(animation, startTime) {
                updateState(internalState)
            }
            val endReason = if (clampingNeeded) BoundReached else Finished
            endAnimation()
            AnimationResult(endState, endReason)
        } catch (e: CancellationException) {
            endAnimation()
            throw e
        }
    }
}

All type‑specific animation paths converge here, ensuring a consistent execution model across color, Dp, Int, Size, Rect, and custom types.

The article concludes that property animations in Compose share a common generic infrastructure; differences lie mainly in the generic type and specific converters used. Readers are encouraged to explore each helper function for deeper understanding.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

AndroidKotlinJetpack ComposeAnimation APIProperty Animation
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.