detectTapGestures 是 Compose 中处理轻触手势的利器。它内部通过时间窗口与距离判定区分单击、双击、长按等。双击事件 onDoubleTap 会在极短时间(通常 300ms)内检测到两次点击时触发。
| 手势类型 | 回调 | 触发条件 | 典型延迟 |
|---|---|---|---|
| 单击 (Tap) | onTap | 单次按压并抬起,无二次点击 | 约 300ms (等待双击超时) |
| 双击 (DoubleTap) | onDoubleTap | 两次快速点击 (间隔 < 300ms) | 第二次按下时立即触发 |
| 长按 (LongPress) | onLongPress | 按压持续 > 500ms | 500ms 后触发 |
当同时监听 onTap 和 onDoubleTap 时,每次单击都会延迟触发,因为系统需要等待“是否会有第二次点击”。这导致单击响应变慢,且双击时也会错误触发单击。这就是经典的 单击/双击冲突。
👆 单击需要等待双击超时窗口,造成延迟;双击时若处理不当会先触发单击。
detectTapGestures 只监听双击,单击用另外的 clickable 或 Modifier.pointerInput 单独处理,但需注意嵌套。detectTapGestures 中只使用 onDoubleTap,单击通过 onTap 但加入延迟标记,或使用 forEachGesture 手动控制。Modifier.pointerInput 实现更精细的点击判定,例如记录上次点击时间戳。onTap 中,并接受 300ms 延迟;或使用 combinedClickable 修饰符(已内置双击/单击分离)。Compose 1.2+ 提供了 Modifier.combinedClickable,内部已处理好双击与单击的冲突,无需手动管理时序。
// 使用 combinedClickable 优雅解决冲突
Modifier.combinedClickable(
onClick = {
// 单击回调 (有轻微延迟,但不会误触)
println("单击")
},
onDoubleClick = {
// 双击回调 (快速响应)
println("双击 👍")
},
onLongClick = {
println("长按")
}
)
下面展示使用 detectTapGestures 同时监听单击与双击,并加入 防抖/冲突标记 的典型模式。
@Composable
fun DoubleTapSample() {
var doubleTapFlag by remember { mutableStateOf(false) }
Box(
modifier = Modifier
.size(200.dp)
.background(Color(0xFFF5E8D8), shape = RoundedCornerShape(28.dp))
.pointerInput(Unit) {
detectTapGestures(
onTap = {
// 延迟执行,等待双击判定
if (!doubleTapFlag) {
// 单击逻辑 (300ms后执行)
println("单击事件")
}
doubleTapFlag = false
},
onDoubleTap = {
doubleTapFlag = true
println("双击事件 🎯")
}
)
}
) {
// 内容
}
}
doubleTapFlag 用于标记双击发生,避免单击误触发。但更简洁的方式是直接使用 combinedClickable。
| 场景 | 推荐方案 | 延迟 |
|---|---|---|
| 仅需双击 | detectTapGestures(onDoubleTap = {}) | 无延迟 |
| 单击 + 双击 | combinedClickable (官方) | 单击延迟 ~300ms |
| 自定义时序 | pointerInput + 时间戳标记 | 可控 |
| 避免冲突 | 使用 onDoubleTap 标记,onTap 中判断 | 可优化 |
🎨 设计理念: 双击是富有节奏感的交互,适合点赞、缩放、收藏等场景。在中小学生风格的 UI 中,双击反馈可以搭配可爱的震动或颜色变化,提升趣味性。