Abhay Sharma
Abhay Sharma

Reputation: 83

How can I animate the item placement in this LazyColumn?

I want to animate the task item placement to the bottom of the list when the task item checked (isDone == true).

This is the code for the Lazy Column:

LazyColumn(
    modifier = Modifier.padding(paddingValues),
    state = listState,
    contentPadding = PaddingValues(16.dp)
) {
    items(tasks) { task ->
        TaskItem(
            task = task,
            onEvent = viewModel::onEvent,
            onItemClick = {
                viewModel.onEvent(TaskEvents.OpenOrCreateTask(task.id))
                containerState = ContainerState.Fullscreen
            },
            modifier = Modifier
                .fillMaxWidth()
                .border(BorderStroke(2.dp, Color.Black))
                .padding(8.dp)
        )
    }
}

This is the data class for the task item (isDone represents if the task is completed or not). I want the completed tasks to appear at the end of the list but also want to animate it.

@Entity
data class Task(
    val title: String,
    val description: String?,
    val isDone: Boolean,
    @PrimaryKey val id: Int? = null,
)

I tried the animateContentPlacement modifier and can't figure anything else.

Upvotes: 1

Views: 138

Answers (2)

tyg
tyg

Reputation: 15579

Using the animateItemPlacement modifier on your TaskItem is the correct way to go (for Compose versions < 1.7, see below), but as the documentation explains, you must explicitly provide a key to items:

When you provide a key via LazyListScope.item/LazyListScope.items this modifier will enable item reordering animations.

When your Task's id is unique and you can guarantee that null does not occur more than once in the list at any given time, you can do it like this:

items(
    items = tasks.sortedBy(Task::isDone),
    key = { it.id ?: 0 },
) { task ->
    // ...
}

Note that tasks.sortedBy(Task::isDone) is used to order the tasks that are done at the bottom of the list.


Keep in mind that animateItemPlacement does not animate new and removed elements, only reordered elements. To improve that behavior the next Compose version (1.7) will deprecate this function and introduce animateItem as a replacement. You can try this out already with the current (at the time of writing) beta version 1.7.0-beta05.

Upvotes: 0

Curious Head
Curious Head

Reputation: 100

You have to use key for the items, and .animateItem() modifier to your child items. Similar to what @Leviathan said,

  1. Key should not be null neither same. // You can combine multiple properties to make a unique key combination

  2. Add .animateItem() to your child item.

    For your case,

    LazyColumn(
        modifier = Modifier.padding(paddingValues),
        state = listState,
        contentPadding = PaddingValues(16.dp)
    ) {
        items(
            items = tasks.sortedBy { it.isDone }, // or sortedByDescending { it.isDone }
            key = { "${ it.id ?: 0 } - ${ it.isDone } - ${ it.title }" }, // You can combine multiple properties to make a unique key combination
        ) { task ->
            TaskItem(
                task = task,
                onEvent = viewModel::onEvent,
                onItemClick = {
                    viewModel.onEvent(TaskEvents.OpenOrCreateTask(task.id))
                    containerState = ContainerState.Fullscreen
                },
                modifier = Modifier
                    .fillMaxWidth()
                    .animateItem() // add this to your child's modifier
                    .border(BorderStroke(2.dp, Color.Black))
                    .padding(8.dp)
            )
        }
    }
    

You are good to go!

Upvotes: 0

Related Questions