借助 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:- 滑动
StartToEnd时Blue- 切换待办事项。 - 滑动
Red时EndToStart- 删除待办事项。 Settled的背景中没有显示任何内容 - 当用户未滑动该项时,背景中不会显示任何内容。- 同样,显示的
Icon会根据滑动方向进行调整: - 当待办事项已完成时,
StartToEnd会显示CheckBox图标;当待办事项未完成时,StartToEnd会显示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对象会使用各自的说明进行初始化:“付账”“买杂货”“去健身房”和“吃晚餐”。
- 在
LazyColumn会显示垂直滚动的todoItems列表。onToggleDone = { todoItem -> ... }是当用户将对象标记为已完成时从TodoListItem内调用的回调函数。它会更新todoItem的isItemDone属性。由于todoItems是mutableStateListOf,因此此更改会触发重组,从而更新界面。onRemove = { todoItem -> ... }是用户移除项时触发的回调函数。它会从todoItems列表中移除特定的todoItem。这也会导致重新组合,并且相应项会从显示的列表中移除。- 系统会对每个
TodoListItem应用animateItem修饰符,以便在项关闭时调用修饰符的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 源文件。