Compose 手势处理完全指南:点击、拖拽与自定义手势

2024-05-14 · 26 min · 手势交互

手势交互是移动应用的核心体验。Compose 提供了从简单点击到复杂多点触控的完整手势支持。本文将深入讲解 Compose 的手势系统,帮助你构建流畅自然的交互体验。

一、点击手势

// 简单点击
Box(
    modifier = Modifier
        .size(100.dp)
        .clickable { println("点击了") }
)

// 组合点击
Box(
    modifier = Modifier
        .combinedClickable(
            onClick = { println("单击") },
            onLongClick = { println("长按") },
            onDoubleClick = { println("双击") }
        )
)

监听点击状态

@Composable
fun InteractiveButton() {
    val interactionSource = remember { MutableInteractionSource() }
    val isPressed by interactionSource.collectIsPressedAsState()
    
    val backgroundColor = if (isPressed) Color.DarkGray else Color.Gray
    
    Box(
        modifier = Modifier
            .background(backgroundColor)
            .clickable(interactionSource = interactionSource, indication = null) { }
    ) {
        Text(if (isPressed) "按下中" else "点击我")
    }
}

二、拖拽手势

自由拖拽

@Composable
fun FreeDraggable() {
    var offset by remember { mutableStateOf(Offset.Zero) }
    
    Box(
        modifier = Modifier
            .offset { IntOffset(offset.x.roundToInt(), offset.y.roundToInt()) }
            .size(50.dp)
            .background(Color.Blue)
            .pointerInput(Unit) {
                detectDragGestures { change, dragAmount ->
                    change.consume()
                    offset += dragAmount
                }
            }
    )
}

三、缩放、旋转、平移

@Composable
fun TransformableBox() {
    var scale by remember { mutableFloatStateOf(1f) }
    var rotation by remember { mutableFloatStateOf(0f) }
    var offset by remember { mutableStateOf(Offset.Zero) }
    
    val state = rememberTransformableState { zoomChange, offsetChange, rotationChange ->
        scale *= zoomChange
        rotation += rotationChange
        offset += offsetChange
    }
    
    Box(
        modifier = Modifier
            .fillMaxSize()
            .transformable(state = state),
        contentAlignment = Alignment.Center
    ) {
        Box(
            modifier = Modifier
                .graphicsLayer {
                    scaleX = scale
                    scaleY = scale
                    rotationZ = rotation
                    translationX = offset.x
                    translationY = offset.y
                }
                .size(100.dp)
                .background(Color.Blue)
        )
    }
}

四、pointerInput:低级手势 API

Box(
    modifier = Modifier
        .pointerInput(Unit) {
            detectTapGestures(
                onPress = { println("按下") },
                onTap = { println("点击") },
                onDoubleTap = { println("双击") },
                onLongPress = { println("长按") }
            )
        }
)

五、嵌套滚动

@Composable
fun CollapsibleToolbar() {
    var toolbarHeight by remember { mutableFloatStateOf(200f) }
    
    val nestedScrollConnection = remember {
        object : NestedScrollConnection {
            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                val delta = available.y
                toolbarHeight = (toolbarHeight + delta).coerceIn(56f, 200f)
                return Offset.Zero
            }
        }
    }
    
    Column(modifier = Modifier.nestedScroll(nestedScrollConnection)) {
        Box(modifier = Modifier.height(toolbarHeight.dp).background(Color.Blue))
        LazyColumn { ... }
    }
}

💡 最佳实践

优先使用高级 API(clickable、draggable、transformable),只在需要自定义行为时使用 pointerInput。使用 graphicsLayer 进行变换以避免触发重组。

总结