Compose - 使用 Reorderable

一、概念

支持列表(Column&Row)、滑动列表(LazyList)、网格(LazyGrid)、瀑布流(LazyStaggeredGrid)。

  • **不同尺寸元素支持:**列表中可以包含大小不一的元素。
  • 可选不可重排项:部分元素可以设置为不响应拖放操作。
  • **多种启动方式:**支持直接拖放和长按启动两种模式。
  • **边缘自动滚动:**拖拽到屏幕边缘时自动滚动列表。
  • **流畅动画效果:**利用 Modifier.animateItem() 实现平滑过渡动画。
  • **拖拽句柄支持:**可以使用子组件作为拖拽句柄,即使设置条目中触发拖动的位置。
  • **触觉反馈集成:**支持拖拽过程中的震动反馈。

|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ReorderableItem() | @Composable fun LazyStaggeredGridItemScope.ReorderableItem( state: ReorderableLazyStaggeredGridState, key: Any, modifier: Modifier = Modifier, enabled: Boolean = true, animateItemModifier: Modifier = Modifier.animateItem(), content: @Composable ReorderableCollectionItemScope.(isDragging: Boolean) -> Unit, ) 包裹可拖动的条目。 |
| draggableHandle() | fun Modifier.draggableHandle( enabled: Boolean = true, interactionSource: MutableInteractionSource? = null, onDragStarted: (startedPosition: Offset) -> Unit = {}, onDragStopped: () -> Unit = {}, dragGestureDetector: DragGestureDetector = DragGestureDetector.Press ): Modifier 设置给条目中触发拖动的位置(拖动范围)。 |
| longPressDraggableHandle() | fun Modifier.longPressDraggableHandle( enabled: Boolean = true, interactionSource: MutableInteractionSource? = null, onDragStarted: (startedPosition: Offset) -> Unit = {}, onDragStopped: () -> Unit = {}, ): Modifier 长按触发拖动。 |

二、添加依赖

最新版本

Kotlin 复制代码
[versions]
reorderable = "3.0.0"

[libraries]
reorderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reorderable" }

三、简单使用

Kotlin 复制代码
val lazyListState = rememberLazyListState()
val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to ->
    //重排后更新列表
    list = list.toMutableList().apply {
        add(to.index, removeAt(from.index))
    }
}

LazyColumn(state = lazyListState) {
    items(list, key = { it.id }) {
        //包裹一下条目内容
        ReorderableItem(reorderableLazyListState, key = it.id) { isDragging ->
            IconButton(
                //设置条目中触发拖动的位置
                modifier = Modifier.draggableHandle()
            )
        }
    }
}

四、进阶使用

4.1 固定不可拖拽的条目

有时需要某些项目保持固定位置,比如标题栏或分隔符。

onMove 中的 from.index 和 to.index 表示项目在 LazyColumn 中的原始索引。若使用了分组头部或尾部,可能需要相应调整这些索引值。

XML 复制代码
val reorderableLazyColumnState = rememberReorderableLazyListState(lazyListState) { from, to ->
    list = list.toMutableList().apply {
        add(to.index - 1, removeAt(from.index - 1))
    }
}
Kotlin 复制代码
items(list) { index, item ->
    if (...) {
        // 渲染固定项目
    } else {
        ReorderableItem(state, key = item.id) {
            // 渲染可拖拽项目
        }
    }
}

4.2 添加震动反馈

XML 复制代码
<uses-permission android:name="android.permission.VIBRATE" />
Kotlin 复制代码
var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val hapticFeedback = LocalHapticFeedback.current
val lazyListState = rememberLazyListState()
val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to ->
    list = list.toMutableList().apply {
        add(to.index, removeAt(from.index))
    }
    hapticFeedback.performHapticFeedback(HapticFeedbackType.SegmentFrequentTick)
}

LazyColumn(
    modifier = Modifier.fillMaxSize(),
    state = lazyListState,
    contentPadding = PaddingValues(8.dp),
    verticalArrangement = Arrangement.spacedBy(8.dp),
) {
    items(list, key = { it }) {
        ReorderableItem(reorderableLazyListState, key = it) { isDragging ->
            val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)
            Surface(shadowElevation = elevation) {
                Row {
                    Text(it, Modifier.padding(horizontal = 8.dp))
                    IconButton(
                        modifier = Modifier.draggableHandle(
                            onDragStarted = {
                                hapticFeedback.performHapticFeedback(HapticFeedbackType.GestureThresholdActivate)
                            },
                            onDragStopped = {
                                hapticFeedback.performHapticFeedback(HapticFeedbackType.GestureEnd)
                            },
                        ),
                        onClick = {},
                    ) {
                        Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
                    }
                }
            }
        }
    }
}

4.3 传递 ReorderableCollectionItemScope 给子组件

Modifier.draggableHandle 和 Modifier.longPressDraggableHandle 仅能在 ReorderableCollectionItemScope 作用域内使用,因此需要传递给子组件。

Kotlin 复制代码
LazyColumn(state = lazyListState) {
    items(list, key = { /* item key */ }) {
        ReorderableItem(reorderableLazyListState, key = /* item key */) { isDragging ->
            MyItem(this)
        }
    }
}

@Composable
fun MyItem(scope: ReorderableCollectionItemScope) {
    IconButton(
        modifier = with(scope) {
            Modifier.draggableHandle()
        }
    )
}

4.4 避开系统栏区域

以下是库作者的解决方案,推荐 Modifier.safeContentPadding() 设置给屏幕级组件更方便,详见

若您的 LazyColumn 在导航栏或状态栏下方显示,可考虑为 rememberReorderableLazyListState 添加 scrollThresholdPadding 参数,将滚动触发区域移出导航栏或状态栏的遮挡范围。

Kotlin 复制代码
val reorderableLazyListState = rememberReorderableLazyListState(
    lazyListState = lazyListState,
    scrollThresholdPadding = WindowInsets.systemBars.asPaddingValues(),
) { from, to ->
    ...
}
相关推荐
帅得不敢出门2 小时前
Android Framework在mk中新增类似PRODUCT_MODEL的变量并传递给buildinfo.sh及prop属性中
android·linux·前端
似霰2 小时前
AIDL Hal 开发笔记7----AIDL HAL 的升级
android·framework·hal
黄大包3 小时前
android MQTT封装
android·mqtt·mt
2501_916007476 小时前
跨平台 App 安全,Flutter、RN、Unity、H5 混合应用加固
android·ios·小程序·https·uni-app·iphone·webview
hinewcc7 小时前
Linux电源管理 - wakelocks
android·linux
你怎么知道我是队长7 小时前
win11系统查看设备配置
android·java·javascript
DevangLic7 小时前
【确认是否安装了 C++ 工具】
android·java·c++
2501_916007477 小时前
不越狱如何查看iOS 应用的详细信息及其文件目录结构
android·macos·ios·小程序·uni-app·cocoa·iphone
龚礼鹏7 小时前
图像显示框架十——BufferQueue的工作流程(基于Android 15源码分析)
android