You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Jetpack Compose实现可拖拽排序列表?

Jetpack Compose 实现可拖拽排序列表完整方案

核心实现不需要引入第三方库,基于Compose原生手势检测、状态管理和列表动画能力即可完成,同时支持列表和网格布局的拖拽排序。


1. 基础状态与数据定义

首先定义可观察的数据列表和拖拽相关状态,列表修改时会自动触发界面重组:

// 示例数据类,必须包含唯一key用于列表动画识别
data class SortItem(val id: Int, val content: String)

@Composable
fun DragSortListDemo() {
    // 可观察列表,元素交换时自动触发重绘
    val itemList = remember {
        mutableStateListOf<SortItem>(
            *(1..20).map { SortItem(it, "列表条目 $it") }.toTypedArray()
        )
    }
    // 记录当前正在拖拽的条目索引,null为无拖拽状态
    var draggingIndex by remember { mutableStateOf<Int?>(null) }
    // 列表状态,用于获取可见条目位置信息
    val listState = rememberLazyListState()
    // 协程作用域,用于拖拽到边缘时自动滚动列表
    val coroutineScope = rememberCoroutineScope()
}

2. 列表条目拖拽手势实现

给每个列表条目添加长按拖拽手势监听,拖拽过程中实时计算目标位置并交换列表元素:

LazyColumn(
    state = listState,
    modifier = Modifier.fillMaxSize()
) {
    items(itemList, key = { it.id }) { item ->
        val currentIndex = itemList.indexOf(item)
        val isDragging = currentIndex == draggingIndex

        Box(
            modifier = Modifier
                .fillMaxWidth()
                .animateItemPlacement() // 条目移动平滑动画
                // 拖拽状态视觉效果:放大+阴影
                .then(
                    if (isDragging) Modifier
                        .scale(1.05f)
                        .shadow(8.dp)
                        .background(Color.White)
                    else Modifier
                )
                // 长按拖拽手势核心逻辑
                .pointerInput(Unit) {
                    detectDragGesturesAfterLongPress(
                        onDragStart = { touchOffset ->
                            // 拖拽开始时匹配当前触摸位置对应的条目索引
                            draggingIndex = listState.layoutInfo.visibleItemsInfo
                                .firstOrNull { itemInfo ->
                                    touchOffset.y >= itemInfo.offset && touchOffset.y <= itemInfo.offset + itemInfo.size
                                }?.index
                        },
                        onDrag = { change, dragAmount ->
                            change.consume()
                            val dragPosY = change.position.y
                            // 计算当前触摸位置对应的目标条目索引
                            val targetIndex = listState.layoutInfo.visibleItemsInfo
                                .firstOrNull { itemInfo ->
                                    dragPosY >= itemInfo.offset && dragPosY <= itemInfo.offset + itemInfo.size
                                }?.index

                            // 索引有效且和当前拖拽索引不一致时,交换元素
                            if (draggingIndex != null && targetIndex != null && draggingIndex != targetIndex) {
                                Collections.swap(itemList, draggingIndex!!, targetIndex)
                                draggingIndex = targetIndex
                            }

                            // 拖拽到列表边缘自动滚动逻辑
                            val scrollThreshold = 100.dp.toPx()
                            val listHeight = listState.layoutInfo.run { viewportEndOffset - viewportStartOffset }
                            when {
                                dragPosY < scrollThreshold -> coroutineScope.launch {
                                    listState.scrollBy(-15f)
                                }
                                dragPosY > listHeight - scrollThreshold -> coroutineScope.launch {
                                    listState.scrollBy(15f)
                                }
                            }
                        },
                        onDragEnd = { draggingIndex = null },
                        onDragCancel = { draggingIndex = null }
                    )
                }
                .padding(16.dp)
        ) {
            Text(text = item.content, fontSize = 16.sp)
        }
    }
}

3. 网格布局适配

如果要实现类似drag-sort-gridview的网格拖拽效果,只需要做两处修改:

  • 把LazyColumn替换为LazyVerticalGrid,设置对应列数
  • 拖拽位置匹配逻辑不需要修改,原生适配网格布局的条目索引计算

4. 可选优化点

  • 拖拽过程中可以给条目添加半透明效果,提升视觉辨识度
  • 拖拽时可以拦截列表的其他点击事件,避免误操作
  • 如果需要支持跨列表拖拽,只需要把列表状态和数据列表提升到父级即可

内容的提问来源于stack exchange,提问作者nigripes Felis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.09 06:51:03