Search code examples
androidandroid-jetpack-composeandroid-jetpack

How to animate items on scroll in Jetpack Compose LazyColumn?


I'm trying to achieve a scroll-based animation in Jetpack Compose, where items in a LazyColumn animate into view as they come on screen. This is similar to how animations could be applied in RecyclerView in the classic Views system, where items animate as they appear in the visible viewport.

I've tried using AnimatedVisibility , animateItemPlacement and animateItem with LazyColumn, but neither approach seems to work as expected. Here’s my current setup:

LazyColumn(
    modifier = Modifier
        .fillMaxWidth()
        .background(MaterialTheme.colorScheme.primaryContainer)
) {
    items(500) {
        Item(modifier = Modifier.animateItem())
    }
}
@Composable
private fun Item(modifier: Modifier = Modifier) {
    Column(
        modifier
            .fillMaxWidth()
            .padding(horizontal = 10.dp, vertical = 5.dp)
            .background(MaterialTheme.colorScheme.background),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            "Jihan Khan",
            style = MaterialTheme.typography.headlineLarge.copy(color = MaterialTheme.colorScheme.onBackground)
        )
        Spacer(Modifier.height(10.dp))
        Text(
            "[email protected]",
            style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onBackground)
        )
    }
}

I also tried wrapping the Item with a Box and applying the animateItemPlacement or animateItem modifier on it, but that didn’t work either. I’m looking for guidance on how to achieve the desired animation effect for LazyColumn items in Jetpack Compose. Any insights would be much appreciated!


Solution

  • animateItemPlacement is superseded by animateItem. That only animates when the list of items changes (i.e. a new item is added or the item order is changed).

    If the list stays the same and you only want to animate when an item becomes visible you can use AnimatedVisibility:

    items(500) {
        Box(Modifier.height(100.dp)) {
            var visible by remember { mutableStateOf(false) }
            LaunchedEffect(Unit) { visible = true }
    
            AnimatedVisibility(visible) {
                Item(/*...*/)
            }
        }
    }
    

    Note:

    1. The AnimatedVisibility only triggers when the visible paramter changes. Therefore it must start with false and needs to be set to true in the LaunchedEffect later on.

    2. When the AnimatedVisibility starts with false the entire content is hidden and won't take up any space in the LazyColumn. Therefore all other items in the LazyColumn are also loaded because they all won't take up space and they will all fit, before the first animation even starts. To prevent that I wrapped AnimatedVisibility in a Box with a defined height that acts as a placeholder, even when AnimatedVisibility is not visible.

      I chose 100.dp, but you want to adjust that to the actual height of your items.

    If the default animation of AnimatedVisibility doesn't appeal to you you can adjust it by passing something else to the enter parameter which defaults to fadeIn() + expandIn() (which animates the transparancy and the size respectively).