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
: TheSwipeToDismissBoxState
state 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
swipeToDismissBoxState
manages the component state. It triggers theconfirmValueChange
callback 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
onToggleDone
lambda, passing the currenttodoItem
. This corresponds with updating the to-do item. - If the item is swiped from end to start, it calls the
onRemove
lambda, passing the currenttodoItem
. This corresponds with deleting the to-do item. it != StartToEnd
: This line returnstrue
if the swipe direction is notStartToEnd
, andfalse
otherwise. Returningfalse
prevents theSwipeToDismissBox
from 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
SwipeToDismissBox
enables 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 thebackgroundContent
appears. Both the normal content and thebackgroundContent
get the full constraints of the parent container to render themselves in. Thecontent
is drawn on top of thebackgroundContent
. In this case:backgroundContent
is implemented as aIcon
with a background color based onSwipeToDismissBoxValue
:Blue
when swipingStartToEnd
— toggling a to-do item.Red
when 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
Icon
that is displayed adapts to the swipe direction: StartToEnd
shows aCheckBox
icon when the to-do item is done and aCheckBoxOutlineBlank
icon when it is not done.EndToStart
displays aDelete
icon.
@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 holdTodoItem
objects. When an item is added or removed from this list, Compose recomposes the parts of the UI that depend on it.- Inside
mutableStateListOf()
, fourTodoItem
objects are initialized with their respective descriptions: "Pay bills", "Buy groceries", "Go to gym", and "Get dinner".
- Inside
LazyColumn
displays a vertically scrolling list oftodoItems
.onToggleDone = { todoItem -> ... }
is a callback function invoked from withinTodoListItem
when the user marks an object as done. It updates theisItemDone
property of thetodoItem
. BecausetodoItems
is 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 specifictodoItem
from thetodoItems
list. This also causes a recomposition, and the item will be removed from the displayed list.- An
animateItem
modifier is applied to eachTodoListItem
so that the modifier'splacementSpec
is 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
drawBehind
draws directly into the canvas behind the content of theIcon
composable.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
OutlinedCard
adds 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.