SwipeToDismissBox 组件允许用户通过向左或向右滑动来关闭或更新某个项。
API Surface
使用 SwipeToDismissBox 可组合项实现由滑动手势触发的操作。关键参数包括:
state:创建的SwipeToDismissBoxState状态,用于存储对滑动项进行计算后生成的值,该值在生成时会触发事件。backgroundContent:一个可自定义的可组合项,显示在内容项后面,在滑动内容时会显露出来。
基本示例:滑动即可更新或关闭
此示例中的代码段展示了一种滑动实现,该实现会在从头到尾滑动时更新项,或在从尾到头滑动时关闭项。
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.") } ) } }
代码要点
swipeToDismissBoxState管理组件状态。与商品互动完成后,它会触发confirmValueChange回调。回调主体会处理不同的可能操作。该回调会返回一个布尔值,用于告知组件是否应显示关闭动画。在这种情况下:- 如果该项从开始位置滑动到结束位置,则会调用
onToggleDonelambda,并传递当前todoItem。这对应于更新待办事项。 - 如果该项从末尾滑动到开头,则会调用
onRemovelambda,并传递当前todoItem。这对应于删除待办事项。 it != StartToEnd:如果滑动方向不是StartToEnd,此行会返回true,否则返回false。返回false可防止SwipeToDismissBox在“切换完成”滑动后立即消失,从而允许进行视觉确认或动画。
- 如果该项从开始位置滑动到结束位置,则会调用
SwipeToDismissBox可在每个项上启用水平滑动互动。在静止状态下,它会显示组件的内部内容,但当用户开始滑动时,内容会移开,并显示backgroundContent。普通内容和backgroundContent都会获得父级容器的完整约束条件,以便在其中呈现自身。content绘制在backgroundContent之上。在这种情况下:backgroundContent实现为Icon,其背景颜色基于SwipeToDismissBoxValue:Blue(滑动StartToEnd时)- 切换待办项。Red(滑动EndToStart时)- 删除待办事项。Settled的背景中不显示任何内容 - 当列表项未被滑动时,背景中不显示任何内容。- 同样,显示的
Icon会根据滑动方向进行调整: StartToEnd会在待办事项已完成时显示CheckBox图标,在待办事项未完成时显示CheckBoxOutlineBlank图标。EndToStart会显示Delete图标。
@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() ) } } }
代码要点
mutableStateListOf(...)会创建一个可观察列表,该列表可容纳TodoItem对象。当从此列表中添加或移除项时,Compose 会重组界面中依赖于该列表的部分。- 在
mutableStateListOf()中,四个TodoItem对象使用各自的说明进行初始化:“Pay bills”(支付账单)、“Buy groceries”(购买杂货)、“Go to gym”(去健身房)和“Get dinner”(吃晚餐)。
- 在
LazyColumn会显示一个垂直滚动的todoItems列表。onToggleDone = { todoItem -> ... }是一个回调函数,当用户将对象标记为“完成”时,系统会从TodoListItem中调用该函数。它会更新todoItem的isItemDone属性。由于todoItems是mutableStateListOf,因此这项更改会触发重组,从而更新界面。onRemove = { todoItem -> ... }是在用户移除商品时触发的回调函数。它会从todoItems列表中移除特定的todoItem。这也会导致重组,并且该项将从显示的列表中移除。animateItem修饰符会应用于每个TodoListItem,以便在内容被关闭时调用修饰符的placementSpec。这会为移除该项以及重新排列列表中其他项的操作添加动画效果。
结果
以下视频演示了上述代码段中的基本滑动关闭功能:
如需查看完整的示例代码,请参阅 GitHub 源文件。
高级示例:在滑动时为背景颜色添加动画效果
以下代码段展示了如何结合使用位置阈值,在滑动时为列表项的背景颜色添加动画效果。
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.") } ) } } }
代码要点
drawBehind会直接在Icon可组合项内容后方的画布中绘制。drawRect()会在画布上绘制一个矩形,并使用指定的Color填充绘制范围的整个边界。
- 滑动时,列表项的背景颜色会使用
lerp平滑过渡。- 对于从
StartToEnd滑动的操作,背景颜色会逐渐从浅灰色变为蓝色。 - 对于从
EndToStart滑动的操作,背景颜色会逐渐从浅灰色变为红色。 - 从一种颜色到下一种颜色的过渡量由
swipeToDismissBoxState.progress确定。
- 对于从
OutlinedCard会在列表项之间添加细微的视觉分隔。
@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() ) } } }
代码要点
- 如需了解此代码的要点,请参阅上一部分中的要点,其中介绍了相同的代码段。
结果
以下视频展示了具有动画背景色的高级功能:
如需查看完整的示例代码,请参阅 GitHub 源文件。