The SwipeToDismissBox component allows a user to dismiss or update an
item by swiping it to the left or right.
API surface
Use the SwipeToDismissBox composable to implement actions that are triggered
by swipe gestures. Key parameters include:
state: TheSwipeToDismissBoxStatestate created to store the value produced by calculations on the swipe item, which triggers events when produced.backgroundContent: A customizable composable displayed behind the item content that is revealed when the content is swiped.
Basic example: Update or dismiss on swipe
The snippets in this example show a swipe implementation that either updates the item when swiped from start to end, or dismisses the item when swiped from end to start.
data class TodoItem( val itemDescription: String, var isItemDone: Boolean = false )
@Composable fun TodoListItem( todoItem: TodoItem, onToggleDone: (TodoItem) -> Unit, onRemove: (TodoItem) -> Unit, modifier: Modifier = Modifier, ) { val swipeToDismissBoxState = rememberSwipeToDismissBoxState( confirmValueChange = { if (it == StartToEnd) onToggleDone(todoItem) else if (it == EndToStart) onRemove(todoItem) // Reset item when toggling done status it != StartToEnd } ) SwipeToDismissBox( state = swipeToDismissBoxState, modifier = modifier.fillMaxSize(), backgroundContent = { when (swipeToDismissBoxState.dismissDirection) { StartToEnd -> { Icon( if (todoItem.isItemDone) Icons.Default.CheckBox else Icons.Default.CheckBoxOutlineBlank, contentDescription = if (todoItem.isItemDone) "Done" else "Not done", modifier = Modifier .fillMaxSize() .background(Color.Blue) .wrapContentSize(Alignment.CenterStart) .padding(12.dp), tint = Color.White ) } EndToStart -> { Icon( imageVector = Icons.Default.Delete, contentDescription = "Remove item", modifier = Modifier .fillMaxSize() .background(Color.Red) .wrapContentSize(Alignment.CenterEnd) .padding(12.dp), tint = Color.White ) } Settled -> {} } } ) { ListItem( headlineContent = { Text(todoItem.itemDescription) }, supportingContent = { Text("swipe me to update or remove.") } ) } }
Key points about the code
swipeToDismissBoxStatemanages the component state. It triggers theconfirmValueChangecallback once the interaction with the item is done. The callback body handles the different possible actions. The callback returns a boolean that tells the component whether it should display a dismiss animation. In this case:- If the item is swiped from start to end, it calls the
onToggleDonelambda, passing the currenttodoItem. This corresponds with updating the to-do item. - If the item is swiped from end to start, it calls the
onRemovelambda, passing the currenttodoItem. This corresponds with deleting the to-do item. it != StartToEnd: This line returnstrueif the swipe direction is notStartToEnd, andfalseotherwise. Returningfalseprevents theSwipeToDismissBoxfrom immediately disappearing after a "toggle done" swipe, allowing for a visual confirmation or animation.
- If the item is swiped from start to end, it calls the
SwipeToDismissBoxenables horizontal swiping interactions on each item. In rest, it shows the inner content of the component, but when a user starts swiping, the content is moved away and thebackgroundContentappears. Both the normal content and thebackgroundContentget the full constraints of the parent container to render themselves in. Thecontentis drawn on top of thebackgroundContent. In this case:backgroundContentis implemented as aIconwith a background color based onSwipeToDismissBoxValue:Bluewhen swipingStartToEnd— toggling a to-do item.Redwhen swipingEndToStart— deleting a to-do item.- Nothing is displayed in the background for
Settled— when the item is not being swiped, nothing is displayed in the background. - Similarly, the
Iconthat is displayed adapts to the swipe direction: StartToEndshows aCheckBoxicon when the to-do item is done and aCheckBoxOutlineBlankicon when it is not done.EndToStartdisplays aDeleteicon.
@Composable private fun SwipeItemExample() { val todoItems = remember { mutableStateListOf( TodoItem("Pay bills"), TodoItem("Buy groceries"), TodoItem("Go to gym"), TodoItem("Get dinner") ) } LazyColumn { items( items = todoItems, key = { it.itemDescription } ) { todoItem -> TodoListItem( todoItem = todoItem, onToggleDone = { todoItem -> todoItem.isItemDone = !todoItem.isItemDone }, onRemove = { todoItem -> todoItems -= todoItem }, modifier = Modifier.animateItem() ) } } }
Key points about the code
mutableStateListOf(...)creates an observable list that can holdTodoItemobjects. When an item is added or removed from this list, Compose recomposes the parts of the UI that depend on it.- Inside
mutableStateListOf(), fourTodoItemobjects are initialized with their respective descriptions: "Pay bills", "Buy groceries", "Go to gym", and "Get dinner".
- Inside
LazyColumndisplays a vertically scrolling list oftodoItems.onToggleDone = { todoItem -> ... }is a callback function invoked from withinTodoListItemwhen the user marks an object as done. It updates theisItemDoneproperty of thetodoItem. BecausetodoItemsis amutableStateListOf, this change triggers a recomposition, updating the UI.onRemove = { todoItem -> ... }is a callback function triggered when the user removes the item. It removes the specifictodoItemfrom thetodoItemslist. This also causes a recomposition, and the item will be removed from the displayed list.- An
animateItemmodifier is applied to eachTodoListItemso that the modifier'splacementSpecis called when the item has been dismissed. This animates the removal of the item, as well as the reordering of other items in the list.
Result
The following video demonstrates the basic swipe-to-dismiss functionality from the preceding snippets:
See the GitHub source file for the full sample code.
Advanced example: Animate background color on swipe
The following snippets show how to incorporate a positional threshold to animate an item's background color on swipe.
data class TodoItem( val itemDescription: String, var isItemDone: Boolean = false )
@Composable fun TodoListItemWithAnimation( todoItem: TodoItem, onToggleDone: (TodoItem) -> Unit, onRemove: (TodoItem) -> Unit, modifier: Modifier = Modifier, ) { val swipeToDismissBoxState = rememberSwipeToDismissBoxState( confirmValueChange = { if (it == StartToEnd) onToggleDone(todoItem) else if (it == EndToStart) onRemove(todoItem) // Reset item when toggling done status it != StartToEnd } ) SwipeToDismissBox( state = swipeToDismissBoxState, modifier = modifier.fillMaxSize(), backgroundContent = { when (swipeToDismissBoxState.dismissDirection) { StartToEnd -> { Icon( if (todoItem.isItemDone) Icons.Default.CheckBox else Icons.Default.CheckBoxOutlineBlank, contentDescription = if (todoItem.isItemDone) "Done" else "Not done", modifier = Modifier .fillMaxSize() .drawBehind { drawRect(lerp(Color.LightGray, Color.Blue, swipeToDismissBoxState.progress)) } .wrapContentSize(Alignment.CenterStart) .padding(12.dp), tint = Color.White ) } EndToStart -> { Icon( imageVector = Icons.Default.Delete, contentDescription = "Remove item", modifier = Modifier .fillMaxSize() .background(lerp(Color.LightGray, Color.Red, swipeToDismissBoxState.progress)) .wrapContentSize(Alignment.CenterEnd) .padding(12.dp), tint = Color.White ) } Settled -> {} } } ) { OutlinedCard(shape = RectangleShape) { ListItem( headlineContent = { Text(todoItem.itemDescription) }, supportingContent = { Text("swipe me to update or remove.") } ) } } }
Key points about the code
drawBehinddraws directly into the canvas behind the content of theIconcomposable.drawRect()draws a rectangle on the canvas and fills the entire bounds of the drawing scope with the specifiedColor.
- When swiping, the background color of the item smoothly transitions using
lerp.- For a swipe from
StartToEnd, the background color gradually changes from light gray to blue. - For a swipe from
EndToStart, the background color gradually changes from light gray to red. - The amount of transition from one color to the next is determined by
swipeToDismissBoxState.progress.
- For a swipe from
OutlinedCardadds a subtle visual separation between the list items.
@Composable private fun SwipeItemWithAnimationExample() { val todoItems = remember { mutableStateListOf( TodoItem("Pay bills"), TodoItem("Buy groceries"), TodoItem("Go to gym"), TodoItem("Get dinner") ) } LazyColumn { items( items = todoItems, key = { it.itemDescription } ) { todoItem -> TodoListItemWithAnimation( todoItem = todoItem, onToggleDone = { todoItem -> todoItem.isItemDone = !todoItem.isItemDone }, onRemove = { todoItem -> todoItems -= todoItem }, modifier = Modifier.animateItem() ) } } }
Key points about the code
- For key points about this code, see Key points from a previous section, which describes an identical code snippet.
Result
The following video shows the advanced functionality with animated background color:
See the GitHub source file for the full sample code.