Deep Dive into Compose Animatable API: Creation, animateTo, snapTo, and a Practical Upload Button Example
This article explains the low‑level Android Jetpack Compose Animatable API, its constructor parameters, how to create and configure Animatable instances for various value types, and demonstrates using animateTo and snapTo within coroutines, including listeners and a complete upload‑button use case.
This article introduces the lower‑level animation API Animatable in Android Jetpack Compose, explains its constructor parameters, shows how to create and configure Animatable instances for different value types, and demonstrates the use of animateTo and snapTo methods, including coroutine handling and animation listeners.
Animatable
When we examined animateXxxAsState earlier, we discovered that it ultimately relies on Animatable . The Animatable class provides the core animation functionality, and higher‑level APIs such as animateValueAsState and animateXxxAsState are thin wrappers around it.
Constructor
The constructor requires three parameters:
initialValue : the starting value of the animation (e.g., Float, Dp, etc.).
typeConverter : a TwoWayConverter that converts between the value type and an AnimationVector . Compose supplies converters for common types such as Float, Int, Dp, Size, Color, etc.
visibilityThreshold : an optional threshold that, when reached, instantly finishes the animation.
class Animatable
(initialValue: T, val typeConverter: TwoWayConverter
, private val visibilityThreshold: T? = null)Creating an Animatable
Example for a Float value:
val animatable = remember { Animatable(100f, Float.VectorConverter) }For other types you pass the corresponding VectorConverter (Dp, Size, Color, etc.). Compose also provides convenience overloads for Float and Color where only the initial value is needed.
animateTo
The animateTo suspend function triggers the animation. Its signature is:
suspend fun animateTo(targetValue: T, animationSpec: AnimationSpec
= defaultSpringSpec, initialVelocity: T = velocity, block: (Animatable
.() -> Unit)? = null): AnimationResultParameters:
targetValue : the destination value.
animationSpec : configuration of the animation (spring, tween, etc.).
initialVelocity : starting velocity.
block : a callback executed on each frame, useful for listening to animation progress.
The function returns an AnimationResult containing the final AnimationState and an AnimationEndReason (either Finished or BoundReached ).
Running the animation
Because animateTo is a suspend function, it must be called from a coroutine. In Compose you can use LaunchedEffect or obtain a coroutine scope with rememberCoroutineScope() . Example using state‑driven animation:
var moveToRight by remember { mutableStateOf(false) }
val animatable = remember { Animatable(10.dp, Dp.VectorConverter) }
LaunchedEffect(moveToRight) {
animatable.animateTo(if (moveToRight) 200.dp else 10.dp)
}
Box(
Modifier
.padding(start = animatable.value, top = 30.dp)
.size(100.dp, 100.dp)
.background(Color.Blue)
.clickable { moveToRight = !moveToRight }
)snapTo
The snapTo suspend function instantly sets the animation value to the target without any interpolation, equivalent to a zero‑duration SnapSpec :
suspend fun snapTo(targetValue: T)It is also called from a coroutine, for example:
val animatable = remember { Animatable(10.dp, Dp.VectorConverter) }
val scope = rememberCoroutineScope()
Box(
Modifier
.padding(start = animatable.value, top = 30.dp)
.size(100.dp, 100.dp)
.background(Color.Blue)
.clickable {
scope.launch { animatable.snapTo(200.dp) }
}
)Practical Example – Upload Button
The article provides a complete composable that animates an upload button through four states (Normal, Start, Uploading, Success) using multiple Animatable instances for background color, text alpha, box width, progress, and progress alpha. The custom animateUploadAsState composable creates the required Animatable s, launches animateTo for each property when the state changes, and returns the interpolated UploadData object.
data class UploadData(
val backgroundColor: Color,
val textAlpha: Float,
val boxWidth: Dp,
val progress: Int,
val progressAlpha: Float
)
@Composable
fun animateUploadAsState(value: UploadData, state: Any): UploadData {
val bgColorAnimatable = remember { Animatable(value.backgroundColor, Color.VectorConverter(value.backgroundColor.colorSpace)) }
val textAlphaAnimatable = remember { Animatable(value.textAlpha) }
val boxWidthAnimatable = remember { Animatable(value.boxWidth, Dp.VectorConverter) }
val progressAnimatable = remember { Animatable(value.progress, Int.VectorConverter) }
val progressAlphaAnimatable = remember { Animatable(value.progressAlpha) }
LaunchedEffect(state) { bgColorAnimatable.animateTo(value.backgroundColor) }
LaunchedEffect(state) { textAlphaAnimatable.animateTo(value.textAlpha) }
LaunchedEffect(state) { boxWidthAnimatable.animateTo(value.boxWidth) }
LaunchedEffect(state) { progressAnimatable.animateTo(value.progress) }
LaunchedEffect(state) { progressAlphaAnimatable.animateTo(value.progressAlpha) }
return UploadData(
bgColorAnimatable.value,
textAlphaAnimatable.value,
boxWidthAnimatable.value,
progressAnimatable.value,
progressAlphaAnimatable.value
)
}The UI composes a box whose size, color, and inner progress indicator animate according to the current UploadData . A secondary button toggles the upload state to demonstrate the animation flow.
Conclusion
The article demonstrates how to work directly with the low‑level Animatable API, covering creation, animateTo , snapTo , animation listeners, and result handling, and shows a real‑world upload‑button implementation that achieves the same effect as the higher‑level animateXxxAsState APIs.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.