手势交互是移动应用的核心体验。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 进行变换以避免触发重组。
总结
- clickable:简单点击,支持涟漪效果
- combinedClickable:单击、双击、长按组合
- draggable:单轴拖拽
- transformable:缩放、旋转、平移
- pointerInput:底层 API,自定义手势逻辑
- nestedScroll:嵌套滚动协调