首页 / Android 入门教程 / Compose 手势处理

Android 入门教程

Compose 手势处理

本教程共 100 篇 · 第 53 篇 · 更新于 2026-07-28 · 约 7 分钟阅读

AndroidAndroid 入门教程Jetpack Compose手势clickablepointerInput拖拽滑动

53. Compose 手势处理

本节目标:掌握 Compose 的各种手势处理方式,学会点击、长按、拖拽、滑动、缩放旋转等交互。

clickable 点击

最简单的手势,前面已多次用到:

Text(
    "点我",
    modifier = Modifier.clickable {
        Log.d("Click", "被点击了")
    }
)

clickable 自带涟漪效果和按压反馈。还能配置点击行为:

Modifier.clickable(
    onClickLabel = "删除",  // 无障碍读屏标签
    role = Role.Button,     // 无障碍角色
    onClick = { /* 处理 */ }
)

combinedClickable 点击 + 长按

同时处理单击和长按:

@OptIn(ExperimentalFoundationApi::class)
Text(
    "长按我试试",
    modifier = Modifier.combinedClickable(
        onClick = { Log.d("Gesture", "单击") },
        onLongClick = { Log.d("Gesture", "长按") },
        onDoubleClick = { Log.d("Gesture", "双击") }
    )
)
Note
  • combinedClickable 是实验性 API,加 @OptIn(ExperimentalFoundationApi::class)。它一次性处理单击、双击、长按三种手势。

pointerInput 底层手势

pointerInput 是更底层的手势处理入口,能监听原始指针事件:

@Composable
fun TouchTracker() {
    Box(
        modifier = Modifier
            .size(200.dp)
            .background(Color.LightGray)
            .pointerInput(Unit) {
                awaitEachGesture {
                    val down = awaitFirstDown()
                    Log.d("Touch", "按下: ${down.position}")

                    while (true) {
                        val event = awaitPointerEvent()
                        event.changes.forEach { change ->
                            Log.d("Touch", "移动: ${change.position}")
                        }
                    }
                }
            }
    )
}

一般不需要这么底层,Compose 提供了更高级的检测函数。

detectDragGestures 拖拽

@Composable
fun DraggableBox() {
    var offset by remember { mutableStateOf(Offset.Zero) }

    Box(
        modifier = Modifier
            .offset { IntOffset(offset.x.toInt(), offset.y.toInt()) }
            .size(80.dp)
            .background(Color.Blue, CircleShape)
            .pointerInput(Unit) {
                detectDragGestures { change, dragAmount ->
                    change.consume()
                    offset += dragAmount
                }
            }
    )
}
  • detectDragGestures 检测拖拽手势,每次移动回调 dragAmount(偏移量)。
  • Modifier.offset { } 用 lambda 版本支持动态位置。
  • change.consume() 标记事件已消费,避免冒泡。

detectDragGesturesAfterLongPress

长按后才能拖拽(类似长按图标进入编辑模式):

.pointerInput(Unit) {
    detectDragGesturesAfterLongPress { change, dragAmount ->
        change.consume()
        offset += dragAmount
    }
}

swipeable 滑动

做滑动开关、滑动删除:

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun SwipeToDismiss() {
    val swipeState = remember {
        AnchoredDraggableState(
            initialValue = 0,
            positionalThreshold = { distance -> distance * 0.5f },
            velocityThreshold = { 125.dp.toPx() },
            animationSpec = tween()
        )
    }
    // 设置锚点:0 = 关闭,1 = 打开
    // 具体实现需要配合 Box 和 offset
}
Tip
  • swipeable 在新版 Compose 中已被 AnchoredDraggable 替代。适合做侧滑菜单、滑动删除等「在几个位置之间吸附」的效果。如果只是简单的水平/垂直拖拽,用 detectHorizontalDragGestures 更直接。

transformable 缩放和旋转

图片查看器那种双指缩放旋转:

@Composable
fun ZoomableImage() {
    var scale by remember { mutableStateOf(1f) }
    var rotation by remember { mutableStateOf(0f) }
    var offset by remember { mutableStateOf(Offset.Zero) }

    Box(
        modifier = Modifier
            .clipToBounds()
            .pointerInput(Unit) {
                detectTransformGestures { _, pan, zoom, rotationChange ->
                    scale *= zoom
                    rotation += rotationChange
                    offset += pan
                }
            }
    ) {
        Image(
            painter = painterResource(R.drawable.photo),
            contentDescription = null,
            modifier = Modifier
                .graphicsLayer(
                    scaleX = scale,
                    scaleY = scale,
                    rotationZ = rotation,
                    translationX = offset.x,
                    translationY = offset.y
                )
        )
    }
}

detectTransformGestures 回调四个值:

  • pan:平移量
  • zoom:缩放倍率(1 = 不变,>1 放大)
  • rotation:旋转角度
  • centroid:手势中心点
Note
  • graphicsLayer 用硬件加速做变换,比直接改 Modifier 属性性能好。缩放、旋转、平移都通过它应用。

scrollable 滚动

让组件可滚动:

@Composable
fun ScrollableColumn() {
    Column(
        modifier = Modifier
            .verticalScroll(rememberScrollState())
    ) {
        repeat(50) {
            Text("第 $it 项", modifier = Modifier.padding(16.dp))
        }
    }
}

verticalScroll 是最简单的滚动方式。LazyColumn 自带滚动,不需要这个。

滚动到指定位置

val scrollState = rememberScrollState()
val scope = rememberCoroutineScope()

Column(modifier = Modifier.verticalScroll(scrollState)) {
    // 内容
}

Button(onClick = {
    scope.launch {
        scrollState.animateScrollTo(1000)  // 平滑滚动到位置
    }
}) {
    Text("滚动到底部")
}

detectTapGestures 点击手势检测

clickable 更灵活的点击检测:

.pointerInput(Unit) {
    detectTapGestures(
        onTap = { offset -> Log.d("Tap", "点击 $offset") },
        onDoubleTap = { offset -> Log.d("Tap", "双击") },
        onLongPress = { offset -> Log.d("Tap", "长按") }
    )
}

手势选择指南

手势需求推荐方案
普通点击clickable
单击 + 双击 + 长按combinedClickabledetectTapGestures
拖拽detectDragGestures
滑动吸附AnchoredDraggable
缩放旋转detectTransformGestures
滚动verticalScroll / LazyColumn
Warning
  • pointerInput 的 key 参数很重要。如果手势逻辑依赖某个变量,把它作为 key,变量变化时手势检测会重新初始化。否则可能用到旧值。

小结

Compose 手势处理从简单到复杂:clickable 做点击,combinedClickable 做多手势,pointerInput + detectXxxGestures 做拖拽缩放旋转。verticalScroll 做滚动。graphicsLayer 做高性能变换。比传统 View 的 onTouchEvent 简洁太多。下一节学习副作用。