AnimationSequence is a powerful yet intuitive Jetpack Compose library designed to simplify sequential animations in your Android apps. Easily orchestrate elegant, responsive, and hierarchical animations with minimal effort, enhancing user experience and bringing your UI to life.
Since version 1.1.0, AnimationSequence supports Jetpack Compose Multiplatform, allowing you to create rich animated UIs not only for Android, but also for iOS, Desktop and Web (Wasm) (where supported).
Available on Maven Central with coordinates: io.github.pauloaapereira:animatedsequence.
- ✅ Simple & Intuitive: Effortlessly animate elements sequentially — no manual index management needed!
- ✅ Multiplatform Ready: Works across Android, iOS, Desktop, and Web (Wasm).
- ✅ Highly Customizable: Full control over individual animations, delays, and transitions.
- ✅ Hierarchical Animations: Seamlessly manage nested animations.
- ✅ Lazy List Support: Beautiful staggered animations for LazyColumn/LazyRow/LazyGrid.
- ✅ Automatic Ordering: Items animate in composition order, or use explicit indices for precise control.
- ✅ Robust: Efficient resource cleanup and error handling built-in.
Here's a simple example to get you started:
AnimationSequenceHost {
AnimatedItem {
Text("Hello, Compose Animations!")
}
AnimatedItem {
Button(onClick = {}) { Text("Animated Button") }
}
}The sample app includes 7 examples showcasing different features. Here's what each one demonstrates:
Items animate sequentially in the order they're composed — no manual index management needed!
Mix automatic and explicit indices. Explicit indices reserve specific slots; auto items fill the gaps.
Beautiful cascading entrance animations for LazyColumn. Items animate once and don't re-animate when scrolled back into view.
Full control over enter/exit transitions per item. Different items can have different animations (scale, slide left, slide right).
Add and remove items with proper enter/exit animations using trackItems(). Removed items animate out before being disposed.
Hierarchical animation hosts. Child animations play after their parent, and when the parent exits, children exit first.
Control animations programmatically with enter() and exit() functions. Perfect for custom triggers and user interactions.
The AnimationSequenceHost composable is the core of this library, managing the sequential animation flow.
Parameters:
modifier: Modifier(optional) - Modifier to be applied to the host container.startByDefault: Boolean(optional, default:true) - Automatically starts animations upon composition if set totrue.content: @Composable (scope: SequentialAnimationScope) -> Unit- The composable content, providing access to theSequentialAnimationScopefor granular animation control.
Provides detailed control over animations:
enter()- Animates all items sequentially.exit(all: Boolean = false)- Animates exit transitions sequentially. Whenallistrue, exits all animations simultaneously.enter(key: Int)- Starts the enter animation for a specific item by its hash key.exit(key: Int)- Starts the exit animation for a specific item by its hash key.isAnimating()- Returnstrueif an animation sequence is currently in progress.
Defines individual animated components within the AnimationSequenceHost.
Automatic Ordering: Items animate in the order they are composed — no need to specify indices manually!
AnimationSequenceHost {
AnimatedItem { Text("Animates 1st") }
AnimatedItem { Text("Animates 2nd") }
AnimatedItem { Text("Animates 3rd") }
}Parameters:
modifier: Modifier(optional) - Modifier applied to the animated content.index: Int?(optional) - Explicit slot to reserve in the animation order. If null, automatically assigned based on composition order.delayAfterAnimation: Long(optional, default:400ms) - Delay before the next animation starts.enter: EnterTransition(optional, default:fadeInwith 300ms duration) - Defines the animation when entering.exit: ExitTransition(optional, default:fadeOutwith 300ms duration) - Defines the animation when exiting.content: @Composable () -> Unit- The content to be animated.
Advanced parameters (for edge cases — prefer LazyAnimationHost for lazy lists):
key: Any?(optional) - Stable key for identity. Use withanimateOnce.animateOnce: Boolean(optional, default:false) - If true, item animates immediately and won't re-animate if it leaves/re-enters composition.
Note: For lazy lists (
LazyColumn,LazyRow, etc.), useLazyAnimationHostwithanimatedItems()instead — it provides staggered animations, scroll tracking, and exit animations out of the box.
Mixing Explicit and Auto Indices:
Explicit indices reserve specific slots; auto items fill the remaining slots:
AnimationSequenceHost {
AnimatedItem { Text("A") } // Auto → slot 0
AnimatedItem(index = 5) { Text("B") } // Explicit → slot 5
AnimatedItem { Text("C") } // Auto → slot 1
}
// Animation order: A (0) → C (1) → B (5)Beautiful staggered animations for LazyColumn, LazyRow, and LazyGrid with a clean, scoped API.
Wrap your lazy list in LazyAnimationHost:
LazyAnimationHost {
LazyColumn {
animatedItems(items, key = { it.id }) { item ->
ListItemContent(item)
}
}
}That's it! Items animate with a staggered fade-in effect and won't re-animate when scrolled back into view.
LazyAnimationHost(staggerDelayMillis = 100) {
LazyColumn {
animatedItems(items, key = { it.id }) { item ->
ListItemContent(item)
}
}
}Full control over enter/exit transitions with access to the calculated stagger delay:
LazyAnimationHost(staggerDelayMillis = 80) {
LazyColumn {
animatedItems(
items = myList,
key = { it.id },
enter = { index, item, staggerDelay ->
// Use staggerDelay in your animation spec!
if (item.isSpecial) {
scaleIn(tween(300, delayMillis = staggerDelay)) +
fadeIn(tween(300, delayMillis = staggerDelay))
} else {
slideInHorizontally(tween(300, delayMillis = staggerDelay)) { it } +
fadeIn(tween(300, delayMillis = staggerDelay))
}
},
exit = { _, _ -> fadeOut() }
) { item ->
ListItemContent(item)
}
}
}staggerDelayMillis: Int(default:50) - Delay between each item's animation.defaultEnter: EnterTransition(default:fadeIn(300ms)) - Default enter transition.defaultExit: ExitTransition(default:fadeOut(300ms)) - Default exit transition.
The scope provides access to useful functions:
reset()- Clears all tracking and restarts stagger timing. Use to replay animations.wasShown(key: Any)- Check if an item has already been animated.
LazyAnimationHost { scope ->
Button(onClick = { scope.reset() }) {
Text("Replay Animations")
}
LazyColumn { ... }
}items: List<T>- The list of items to display.key: (T) -> Any- Required. Stable, unique key for each item.enter: ((index, item, staggerDelay) -> EnterTransition)?- Custom enter transition per item.exit: ((index, item) -> ExitTransition)?- Custom exit transition per item.
Note: Stagger is automatically applied only to initially visible items. Items scrolled into view later animate immediately without stagger delay.
For lists where items are dynamically added/removed and you need exit animations, use scope.trackItems():
var items by remember { mutableStateOf(listOf(1, 2, 3)) }
LazyAnimationHost { scope ->
// trackItems manages visibility state for proper exit animations
val animatedList = scope.trackItems(items, key = { it })
LazyColumn {
animatedItems(
items = animatedList,
enter = { _, _, _ -> slideInHorizontally { it } + fadeIn() },
exit = { _, _ -> slideOutHorizontally { -it } + fadeOut() }
) { animatedItem ->
ItemCard(
item = animatedItem.data, // Access actual data via .data
onRemove = { items = items - animatedItem.data }
)
}
}
}Why is this needed? When items are removed from a LazyColumn, the composable is immediately disposed. trackItems() keeps removed items in composition until their exit animation completes.
- ✅ Scoped State: All animation state is scoped to the host — no global state!
- ✅ Automatic Reset: Navigate away and back, animations replay automatically.
- ✅ Clean API: No manual state management needed.
- ✅ Consistent with AnimationSequenceHost: Familiar pattern for the whole library.
| Feature | AnimatedVisibility alone |
LazyAnimationHost |
|---|---|---|
| Stagger timing | ❌ Manual calculation | ✅ Automatic cascade |
| No re-animation on scroll | ❌ Track yourself | ✅ Built-in |
| Exit animations on remove | ❌ Item disposed immediately | ✅ trackItems() handles it |
| Replay all animations | ❌ Manual state reset | ✅ scope.reset() |
| Scoped cleanup | ❌ Manual | ✅ Automatic |
AnimatedVisibility is low-level — you manage everything yourself. LazyAnimationHost gives you beautiful cascading animations, smart scroll tracking, and proper exit animations with minimal code.
Use SequentialAnimationScope for detailed control:
AnimationSequenceHost(startByDefault = false) { scope ->
LaunchedEffect(Unit) {
scope.enter() // Start all animations sequentially
}
AnimatedItem {
Text("First Animated Item")
}
AnimatedItem {
Text("Second Animated Item")
}
AnimatedItem {
Text("Third Animated Item")
}
}Effortlessly manage nested animations:
AnimationSequenceHost {
AnimatedItem {
Text("Parent Animated Text")
}
AnimationSequenceHost {
AnimatedItem {
Text("Child Animated Text 1")
}
AnimatedItem {
Text("Child Animated Text 2")
}
}
}When the parent exits, all children automatically perform their exit animations sequentially.
Good news! The library is fully backward compatible. Your existing code will continue to work without any changes.
-
Automatic Indexing: The
indexparameter is now optional. Items automatically animate in composition order:// Before (still works!) AnimatedItem(index = 0) { Text("First") } AnimatedItem(index = 1) { Text("Second") } // After (simpler!) AnimatedItem { Text("First") } AnimatedItem { Text("Second") }
-
Lazy List Support: New
LazyAnimationHostandanimatedItemsfor beautiful staggered animations in lazy lists.
None! All existing APIs work exactly as before.
Step 1. Add the Maven Central repository (if not already included)**
In your settings.gradle.kts (or settings.gradle):
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
// others ...
}
}Step 2. Add the dependency in your build.gradle.kts:
dependencies {
implementation("io.github.pauloaapereira:animatedsequence:<version>")
}Replace <version> with the latest release version.
Want to see AnimationSequence in action? We've included a full multiplatform sample app in the sample/ folder with targets for Android, iOS, Desktop, and Web.
- Clone the repository
git clone https://github.com/pauloaapereira/AnimatedSequence.git
cd AnimatedSequenceIf you're using Android Studio, the Android sample should be runnable right away.
The IDE will automatically detect the run configuration from the androidApp module.
You may need to manually create a run configuration the first time:
- Go to Run > Edit Configurations
- Click the
+button → select iOS Application - Select the
.xcodeprojfile inside theiosApp/folder (root of the project) - Choose the
iosAppscheme and Debug configuration - Select an execution target (usually auto-selected)
✅ You can now run the iOS sample on a simulator or a real device.
To run the desktop sample:
./gradlew :sample:runTo run the WebAssembly sample in development mode:
./gradlew :sample:wasmJsBrowserDevelopmentRun -t🔁 The
-tflag enables continuous build, so it will reload when you make changes.
🌐 The app will open in your default browser, usually at http://localhost:8080.
You can explore and modify each sample target in the sample/ folder as you'd like.
If you run into any issues or missing configurations, feel free to open an issue — contributions and improvements are welcome!
Contributions, issues, and feature requests are welcome! Feel free to check the issues page.
Distributed under the Apache License 2.0.
Feel free to support me and my new content on:
Happy Animating! ✨🚀








