Skip to content

手势 (Gestures)

源:Compose 中的手势

Compose 提供了多种处理用户触控输入的方式,从简单的点击到复杂的多点触控和嵌套滚动。

简单的点击与交互

最常用的方式是使用 Modifier.clickable 等高级修饰符。

kotlin
Text(
    text = "Click Me",
    modifier = Modifier
        .clickable { /* 处理点击 */ }
        .padding(16.dp)
)

其他类似的修饰符:

  • combinedClickable: 支持双击和长按。
  • draggable: 监听单向(水平或垂直)拖拽。
  • swipeable: 用于实现滑动开关或底部抽屉等吸附效果。
  • scrollable: 使无滚动逻辑的组件支持滚动手势(通常不需要手动使用,LazyColumn 已内置)。

指针输入 (Pointer Input)

对于更复杂的自定义手势,可以使用 Modifier.pointerInput。这是一个构建块 API,通常与 detect*Gestures 系列函数配合使用。

1. 点击手势 (Tap)

kotlin
Box(
    modifier = Modifier
        .fillMaxSize()
        .pointerInput(Unit) {
            detectTapGestures(
                onTap = { offset -> /* 单击 */ },
                onDoubleTap = { offset -> /* 双击 */ },
                onLongPress = { offset -> /* 长按 */ },
                onPress = { offset ->
                    // 按下时触发
                    // 必须调用 tryAwaitRelease() 等待释放
                    tryAwaitRelease() 
                    // 释放时触发
                }
            )
        }
)

2. 拖拽手势 (Drag)

kotlin
var offsetX by remember { mutableStateOf(0f) }
var offsetY by remember { mutableStateOf(0f) }

Box(
    modifier = Modifier
        .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
        .pointerInput(Unit) {
            detectDragGestures { change, dragAmount ->
                change.consume() // 消费事件,防止向上传递
                offsetX += dragAmount.x
                offsetY += dragAmount.y
            }
        }
)

3. 变换手势 (Transform)

支持多点触控的平移、缩放和旋转。

kotlin
var scale by remember { mutableStateOf(1f) }
var rotation by remember { mutableStateOf(0f) }
var offset by remember { mutableStateOf(Offset.Zero) }

Box(
    modifier = Modifier
        .graphicsLayer(
            scaleX = scale,
            scaleY = scale,
            rotationZ = rotation,
            translationX = offset.x,
            translationY = offset.y
        )
        .pointerInput(Unit) {
            detectTransformGestures { _, pan, zoom, rotate ->
                scale *= zoom
                rotation += rotate
                offset += pan
            }
        }
)

事件分发与消费

在 Compose 中,指针事件通过 UI 树分发:

  1. 初始阶段 (Initial pass): 从根节点向下传递到目标节点。
  2. 主阶段 (Main pass): 从目标节点向上传递回根节点。

PointerInputChange 对象上调用 consume() 可以阻止其他组件处理该事件。

冲突处理

如果在同一个组件或父子组件上使用了相互冲突的手势(例如父容器是 clickable,子容器检测 tap),Compose 会尝试合理处理,但有时需要手动调整。

例如,如果你在 Scrollable 容器(如 LazyColumn)中使用 detectDragGestures,可能需要确保只有特定方向的拖拽被捕获,以免干扰列表滚动。

嵌套滚动 (Nested Scrolling)

Compose 提供了 NestedScrollConnection 来处理父子组件之间的滚动协调(例如,列表滚动时隐藏顶部的 Toolbar)。

kotlin
val nestedScrollConnection = remember {
    object : NestedScrollConnection {
        override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
            // 在子组件滚动之前,父组件有机会先消费滚动距离
            return Offset.Zero
        }

        override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
            // 子组件滚动之后,父组件可以处理剩余的滚动距离
            return Offset.Zero
        }
    }
}

Box(
    modifier = Modifier.nestedScroll(nestedScrollConnection)
) {
    LazyColumn { ... }
}