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

使用Jetpack Compose实现网格拖拽吸附功能遇到的问题

Jetpack Compose拖拽吸附逻辑失效的解决方案

问题根源

  • 坐标单位不统一:你用dp维护可移动Box的偏移量,但boundsInRoot()返回的是屏幕像素坐标,两者直接比较完全不匹配,导致范围判断永远不成立。
  • 判断逻辑不符合需求:仅检查可移动Box的左上角是否在静态Box内,不符合“部分进入即吸附”的要求;同时最终设置的是静态Box左上角坐标,而非中心位置。

修复步骤

  1. 统一坐标单位:改用像素维护可移动Box的偏移量,避免dp与像素的转换误差。
  2. 修正范围判断:检查两个矩形是否重叠(满足部分进入的触发条件),而非单点是否在范围内。
  3. 实现中心吸附:计算静态Box的中心坐标,将可移动Box的中心对齐到该位置。

修改后的完整代码

@Composable
fun DragAndSnapDemo() {
    // 用像素维护可移动Box的偏移量(初始设为0像素)
    var offsetX by remember { mutableStateOf(0f) }
    var offsetY by remember { mutableStateOf(0f) }

    // 静态Box的像素坐标范围
    var staticBoxBounds by remember { mutableStateOf(Rect.Zero) }
    // 可移动Box的像素尺寸,用于计算自身范围
    var movableBoxSize by remember { mutableStateOf(IntSize.Zero) }

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.SpaceEvenly
    ) {
        // 可移动Box
        Box(
            modifier = Modifier
                .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
                .zIndex(2f)
                .background(Color.LightGray)
                .pointerInput(Unit) {
                    detectDragGesturesAfterLongPress(
                        onDrag = { change, dragAmount ->
                            // 直接累加像素偏移量
                            offsetX += dragAmount.x
                            offsetY += dragAmount.y
                            change.consume()
                        },
                        onDragEnd = {
                            // 计算可移动Box的实际像素范围
                            val movableBoxBounds = Rect(
                                left = offsetX,
                                top = offsetY,
                                right = offsetX + movableBoxSize.width,
                                bottom = offsetY + movableBoxSize.height
                            )

                            // 判断两个矩形是否有重叠(部分进入即触发吸附)
                            if (movableBoxBounds.overlaps(staticBoxBounds)) {
                                // 计算静态Box中心,将可移动Box中心对齐过去
                                val staticCenterX = staticBoxBounds.centerX() - movableBoxSize.width / 2f
                                val staticCenterY = staticBoxBounds.centerY() - movableBoxSize.height / 2f
                                offsetX = staticCenterX
                                offsetY = staticCenterY
                            }
                        }
                    )
                }
                .onGloballyPositioned { movableBoxSize = it.size }
                .size(150.dp)
        )

        // 静态Box
        Box(
            modifier = Modifier
                .onGloballyPositioned { staticBoxBounds = it.boundsInRoot() }
                .zIndex(1f)
                .background(Color.DarkGray)
                .size(150.dp)
        )
    }
}

关键修改说明

  • 将偏移量从dp单位改为像素单位,使用offset { IntOffset(...) }直接绑定像素坐标,消除单位转换差异。
  • 通过onGloballyPositioned获取可移动Box的实际像素尺寸,用于构建自身的范围矩形。
  • 用Rect.overlaps()替代单点判断,确保只要两个Box有重叠就触发吸附。
  • 计算静态Box的中心坐标,将可移动Box的中心对齐,实现符合需求的吸附效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 17:25:58