Stefan
Stefan

Reputation: 4231

AnimatedVisibility & SwipeToDismiss Enter Animation does not trigger - Jetpack Compose

Okay so I've been trying to implement swipe to delete function in my app. Whenever I swipe one of the items from the list I'm able to see a RedBackground behind and everything works fine. Also the swipe animation when I delete an item is triggered successfully. (Even though I'm not sure if it's a good idea to use delay for that? I can't think of any other way to do it). But the enter animation when I add an item to the database/list is not working, and I'm not sure why. Here's the code of my Lazy Column

@Composable
fun DisplayTasks(
    tasks: List<ToDoTask>,
    onSwipeToDelete: (Action, ToDoTask) -> Unit,
    navigateToTaskScreen: (Int) -> Unit
) {
    LazyColumn {
        items(
            items = tasks,
            key = { task ->
                task.id
            }
        ) { task ->
            val dismissState = rememberDismissState()
            val dismissDirection = dismissState.dismissDirection
            val isDismissed = dismissState.isDismissed(DismissDirection.EndToStart)

            if (isDismissed && dismissDirection == DismissDirection.EndToStart
            ) {
                val scope = rememberCoroutineScope()
                scope.launch {
                    delay(300)
                    onSwipeToDelete(Action.DELETE, task)
                }
            }

            AnimatedVisibility(
                visible = !isDismissed,
                exit = shrinkVertically(
                    animationSpec = tween(
                        durationMillis = 300,
                    )
                ),
                enter = expandVertically(
                    animationSpec = tween(
                        durationMillis = 300
                    )
                )
            ) {
                SwipeToDismiss(
                    state = dismissState,
                    directions = setOf(DismissDirection.EndToStart),
                    dismissThresholds = { FractionalThreshold(0.2f) },
                    background = { RedBackground() },
                    dismissContent = {
                        LazyColumnItem(
                            toDoTask = task,
                            navigateToTaskScreen = navigateToTaskScreen
                        )
                    }
                )
            }
        }
    }

}

Upvotes: 3

Views: 3250

Answers (1)

Phil Dukhov
Phil Dukhov

Reputation: 87794

First of all, you shouldn't perform any state changing actions inside composable. Instead use one of side effects, usually LaunchedEffect(key) { }: content of the block will be called on the first render and each time key is different from the last render. Also inside you're already in a coroutine scope, so no need to launch it. Check out more about side-effects in the documentation.

Item animation in the list is not yet supported. It's as simple as adding AnimatedVisibility to the items.

When compose firstly sees AnimatedVisibility in the compose tree, it draws(or not draws) it without animation.

And when on next recomposition visible is different from the last render time, it animates.

So to make it work as you wish you can do the following:

  1. Add itemAppeared state value, which will make item in the list initially hidden, and using side effect make it visible right after render
  2. Add columnAppeared which will prevent initial appearance animation - without it when screen renders all items will appear animatedly too
@Composable
fun DisplayTasks(
    tasks: List<ToDoTask>,
    onSwipeToDelete: (Action, ToDoTask) -> Unit,
) {
    var columnAppeared by remember { mutableStateOf(false) }
    LaunchedEffect(Unit) {
        columnAppeared = true
    }
    LazyColumn {
        items(
            items = tasks,
            key = { task ->
                task.id
            }
        ) { task ->
            val dismissState = rememberDismissState()
            val dismissDirection = dismissState.dismissDirection
            val isDismissed = dismissState.isDismissed(DismissDirection.EndToStart)
            if (isDismissed && dismissDirection == DismissDirection.EndToStart
            ) {
                LaunchedEffect(Unit) {
                    delay(300)
                    onSwipeToDelete(Action.DELETE, task)
                }
            }

            var itemAppeared by remember { mutableStateOf(!columnAppeared) }
            LaunchedEffect(Unit) {
                itemAppeared = true
            }
            AnimatedVisibility(
                visible = itemAppeared && !isDismissed,
                exit = shrinkVertically(
                    animationSpec = tween(
                        durationMillis = 300,
                    )
                ),
                enter = expandVertically(
                    animationSpec = tween(
                        durationMillis = 300
                    )
                )
            ) {
                SwipeToDismiss(
                    state = dismissState,
                    directions = setOf(DismissDirection.EndToStart),
                    dismissThresholds = { FractionalThreshold(0.2f) },
                    background = {
                        Box(
                            Modifier
                                .background(Color.Red)
                                .fillMaxSize()
                        )
                    },
                    dismissContent = {
                        Text(task.id)
                    }
                )
            }
        }
    }
}

Upvotes: 4

Related Questions