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

如何在Jetpack Compose中检测跨Composable的滑动手势?

触摸屏缺陷检测应用的滑动手势检测问题

我正在开发一款带触摸屏缺陷检测功能的应用,里面有个网格组件,用户需要触摸网格来确认对应屏幕区域正常工作。点击事件处理没问题,但滑动手势检测遇到了麻烦——没法检测起始于其他Composable的滑动。比如5×5的网格,位置用X、Y索引(像1-1、1-2这类)标识,我需要识别滑动过程中经过的所有网格,比如从4-5滑到4-4再到4-3,这三个网格都得记录下来。

我试过在pointerInput修饰符里用各种方法,但拖拽事件传不到其他Composable里,只能记录滑动起始的第一个网格;还试过坐标匹配,但PointerChangeEvent只给相对位置,不是绝对位置。请问该怎么识别滑动从开始到结束经过的所有Composable?

当前代码

val screenWidth = LocalConfiguration.current.screenWidthDp.dp
var selectedIndex by remember { mutableStateOf(emptySet<Int>()) }

LazyHorizontalGrid(
    rows = GridCells.Fixed(20),
    modifier = Modifier
        .fillMaxSize(),
) {
    items(240) { index ->
        val isSelected by remember {
            derivedStateOf { index in selectedIndex }
        }

        Box(
            modifier = Modifier
                .fillMaxHeight()
                .width(screenWidth / 12)
                .background(if (isSelected) Colors.white else Colors.colorPrimary)
                .border(Dimensions.SIZE_1, Color.White)
                .pointerInput(Unit) {
                    this.awaitPointerEventScope {
                        val event = awaitPointerEvent()
                        when (event.type) {
                            PointerEventType.Release ->
                                // do something
                            else ->
                                selectedIndex += index
                        }
                    }
                }
        )
    }
}

解决方法

1. 把触摸事件统一放到父容器处理

单个子Composable的pointerInput会拦截触摸事件,导致滑动到其他子组件时事件无法传递。应该将触摸逻辑挂载到LazyHorizontalGrid的修饰符上,这样能捕获整个网格区域的所有触摸/滑动动作。

2. 通过绝对坐标计算对应网格索引

利用触摸事件的绝对坐标,结合网格的行列布局规则,计算当前触摸点落在哪个网格:

  • 先明确网格的行列数、每个网格的宽高(含间距,如有)
  • 使用PointerInputChange.position获取触摸点相对于父容器的绝对坐标
  • 根据行列布局,将坐标转换为对应的行、列索引,再映射成网格的全局索引

3. 跟踪滑动全程的网格

在拖拽过程中持续监听位置变化,每次更新都计算当前网格索引,将未记录的索引加入选中集合。

示例代码

val screenWidth = LocalConfiguration.current.screenWidthDp.dp
val gridRows = 20
val gridColumns = 12 // 240个item对应20行12列
var selectedIndex by remember { mutableStateOf(mutableSetOf<Int>()) }
val screenHeight = LocalConfiguration.current.screenHeightDp.dp

LazyHorizontalGrid(
    rows = GridCells.Fixed(gridRows),
    modifier = Modifier
        .fillMaxSize()
        .pointerInput(Unit) {
            detectDragGestures(
                onDragStart = { offset ->
                    // 计算起始点对应的网格索引并加入集合
                    val index = calculateGridIndex(offset, gridRows, gridColumns, screenWidth, screenHeight)
                    index?.let { selectedIndex.add(it) }
                },
                onDrag = { change, _ ->
                    // 滑动过程中持续计算当前触摸点的网格索引
                    val index = calculateGridIndex(change.position, gridRows, gridColumns, screenWidth, screenHeight)
                    index?.let {
                        if (!selectedIndex.contains(it)) {
                            selectedIndex.add(it)
                        }
                    }
                    change.consume()
                },
                onDragEnd = {
                    // 滑动结束后的逻辑,比如重置或保存检测结果
                }
            )
        },
) {
    items(240) { index ->
        val isSelected by remember {
            derivedStateOf { index in selectedIndex }
        }

        Box(
            modifier = Modifier
                .fillMaxHeight()
                .width(screenWidth / gridColumns)
                .background(if (isSelected) Colors.white else Colors.colorPrimary)
                .border(Dimensions.SIZE_1, Color.White)
        )
    }
}

// 辅助函数:根据触摸坐标计算对应网格索引
fun calculateGridIndex(
    offset: Offset,
    rows: Int,
    columns: Int,
    screenWidth: Dp,
    screenHeight: Dp
): Int? {
    val columnWidth = screenWidth.toPx() / columns
    val rowHeight = screenHeight.toPx() / rows

    // 计算列、行索引
    val column = (offset.x / columnWidth).toInt()
    val row = (offset.y / rowHeight).toInt()

    // 校验坐标是否在网格范围内
    if (column in 0 until columns && row in 0 until rows) {
        return row * columns + column
    }
    return null
}

关键说明

  • detectDragGestures是Compose内置的拖拽手势检测API,能监听拖拽的开始、过程和结束三个阶段
  • 父容器的pointerInput中,change.position是相对于父容器的绝对坐标,可直接用于网格位置计算
  • 如果网格包含间距,需要在辅助函数中把间距纳入宽高计算逻辑,避免索引偏差

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 06:17:32