Compose 中的拖放:可重新排序的列表 & 滑动删除
Source: Dev.to
检测拖拽手势
detectDragGesturesAfterLongPress 修饰符在长按后启用拖拽交互:
var offsetX by remember { mutableFloatStateOf(0f) }
var offsetY by remember { mutableFloatStateOf(0f) }
Box(
modifier = Modifier
.size(100.dp)
.background(Color.Blue)
.pointerInput(Unit) {
detectDragGesturesAfterLongPress(
onDrag = { change, dragAmount ->
change.consume()
offsetX += dragAmount.x
offsetY += dragAmount.y
},
onDragEnd = {
// Handle drag completion
}
)
}
)
用于视觉反馈的 GraphicsLayer
使用 graphicsLayer 在拖拽操作期间提供实时视觉反馈:
Box(
modifier = Modifier
.size(100.dp)
.background(Color.Blue)
.graphicsLayer {
translationX = offsetX
translationY = offsetY
scaleX = if (isDragging) 1.1f else 1f
scaleY = if (isDragging) 1.1f else 1f
}
.pointerInput(Unit) {
detectDragGesturesAfterLongPress(
onDragStart = { isDragging = true },
onDrag = { change, dragAmount ->
change.consume()
offsetX += dragAmount.x
offsetY += dragAmount.y
},
onDragEnd = {
isDragging = false
// 动画返回原始位置
}
)
}
)
拖动手柄图标
使用拖动手柄图标来表示项目是可拖动的:
@Composable
fun DraggableListItem(
item: String,
onDrag: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
.pointerInput(Unit) {
detectDragGesturesAfterLongPress(
onDragStart = onDrag,
onDrag = { change, _ -> change.consume() }
)
},
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.DragHandle,
contentDescription = "Drag to reorder",
modifier = Modifier
.padding(end = 8.dp)
.alpha(0.6f)
)
Text(item, modifier = Modifier.weight(1f))
}
}
SwipeToDismissBox 用于滑动删除
SwipeToDismissBox 可组合函数提供了滑动删除模式,并支持自定义背景内容:
@Composable
fun SwipeDeleteItem(
item: String,
onDelete: () -> Unit,
modifier: Modifier = Modifier
) {
SwipeToDismissBox(
modifier = modifier,
backgroundContent = {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Red)
.padding(16.dp),
contentAlignment = Alignment.CenterEnd
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Delete",
tint = Color.White
)
}
},
content = {
ListItem(
headlineContent = { Text(item) },
modifier = Modifier.background(Color.White)
)
},
onDismissed = { onDelete() }
)
}
完整的可重新排序列表
将这些模式组合起来,以实现完整的可重新排序列表并支持滑动删除:
@Composable
fun ReorderableListScreen() {
var items by remember { mutableStateOf(listOf("Item 1", "Item 2", "Item 3")) }
var draggedItem by remember { mutableStateOf<String?>(null) }
LazyColumn(modifier = Modifier.fillMaxSize()) {
itemsIndexed(
items = items,
key = { _, item -> item }
) { index, item ->
SwipeToDismissBox(
modifier = Modifier
.animateItem(
fadeInSpec = tween(300),
fadeOutSpec = tween(300),
placementSpec = spring()
)
.pointerInput(Unit) {
detectDragGesturesAfterLongPress(
onDragStart = { draggedItem = item },
onDrag = { change, _ -> change.consume() },
onDragEnd = { draggedItem = null }
)
},
backgroundContent = {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Red)
.padding(16.dp),
contentAlignment = Alignment.CenterEnd
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Delete",
tint = Color.White
)
}
},
content = {
ListItem(
headlineContent = { Text(item) },
modifier = Modifier.background(
if (draggedItem == item) Color.LightGray else Color.White
)
)
},
onDismissed = {
items = items.toMutableList().also { it.remove(item) }
}
)
}
}
}
Swipe‑to‑Dismiss & Drag‑to‑Reorder 示例
@Composable
fun SwipeAndDragList() {
var items by remember { mutableStateOf(listOf("Item 1", "Item 2", "Item 3")) }
LazyColumn {
itemsIndexed(
items = items,
key = { _, item -> item }
) { index, item ->
SwipeToDismiss(
state = rememberDismissState(),
background = {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Red),
contentAlignment = Alignment.CenterEnd
) {
Icon(Icons.Default.Delete, contentDescription = null)
}
},
content = {
Row(
modifier = Modifier
.fillMaxWidth()
.background(Color.White)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Default.DragHandle, contentDescription = null)
Text(item, modifier = Modifier.weight(1f))
}
},
onDismissed = {
items = items.filterNot { it == item }
}
)
}
}
}
性能优化
在 itemsIndexed() 中使用 key 参数以便正确跟踪组合:
itemsIndexed(
items = items,
key = { _, item -> item.id } // 唯一且稳定的标识符
) { index, item ->
// Your content
}
提供稳定的键可以确保在列表重新排序时动画和状态管理能够正确工作。
最佳实践
- 使用稳定键 – 始终为列表项提供唯一且稳定的标识符。
- 视觉反馈 – 在拖动过程中显示缩放/透明度变化,以提升用户体验。
- 可访问性 – 为拖动手柄和删除操作添加清晰的标签。
- 性能 – 避免不必要地重新组合未被拖动的项。
- 动画 – 使用
animateItem()实现平滑的列表过渡。
Jetpack Compose 的手势检测和 Modifier 系统使得构建复杂、交互式且响应迅速的列表变得轻而易举。
8 Android App Templates →
