👆 点击事件 Modifier.clickable

📘 onClick 回调 · clickable 与 pointerInput 核心差异

🎒 中小学生色系课堂 在 Jetpack Compose 中,点击是最基础也最强大的手势。我们深入 Modifier.clickable 的回调机制, 并理清它与底层 Modifier.pointerInput 的适用边界。

📌 1. Modifier.clickable · 开箱即用

Modifier.clickable 是 Compose 为通用点击场景提供的高级抽象。它封装了按下、抬起、视觉反馈(涟漪)、无障碍等行为。

✅ 默认涟漪效果
✅ 无障碍支持
✅ 防连点 (debounce)
✅ 语义化点击

🔹 基础 onClick 回调

// 最简单的点击 —— 自带波纹与无障碍
Box(
    modifier = Modifier
        .size(120.dp)
        .background(Color(0xFFFAD7A0))
        .clickable { 
            // onClick 回调
            println("📣 按钮被点击")
        }
) {
    Text("点我", modifier = Modifier.align(Alignment.Center))
}

只需一个 lambda,Compose 自动处理触摸消抖、视觉反馈、无障碍事件。适合 80% 的点击场景。

⚙️ 2. onClick 回调与定制参数

clickable 不止一个 lambda,还支持 interactionSource、indication、enabled 等。

参数类型说明学生色系示例
onClick() -> Unit点击回调,核心事件lambda
enabledBoolean是否响应点击,false 时禁用视觉与回调灰化
interactionSourceMutableInteractionSource收集按压/拖拽等交互状态,可自定义涟漪⭐ 常用
indicationIndication?视觉反馈(涟漪/高亮),null 取消效果无波纹
// 定制 enabled 与 interactionSource
val interactionSource = remember { MutableInteractionSource() }

Box(
    modifier = Modifier
        .size(140.dp, 60.dp)
        .background(if (enabled) Color(0xFFB5D3E8) else Color.LightGray)
        .clickable(
            enabled = enabled,
            interactionSource = interactionSource,
            indication = rememberRipple(bounded = true)
        ) {
            println("✅ 可点击状态: $enabled")
        }
) {
    Text("开关点击", modifier = Modifier.align(Alignment.Center))
}

⚡ 3. clickable 与 pointerInput 的本质区别

Modifier.pointerInput 是 Compose 底层手势原始 API,而 clickable 基于它构建,但提供了更高层的封装。

对比维度✅ Modifier.clickable⚡ Modifier.pointerInput
抽象层级高级语义,开箱即用原始事件流 (原始指针)
事件粒度封装为 onClick,忽略细节可监听 Down / Move / Up / 取消
视觉反馈内置 Indication (涟漪)无,需自行实现
无障碍自动生成语义点击需手动添加 semantics
防抖 / 双击内置防连点 (约 300ms)需自行控制
适用场景按钮、卡片、列表项等通用点击自定义手势 (长按、拖拽、双击、多点)

🔸 什么时候用 pointerInput 代替 clickable?

📐 4. 代码对比 · clickable vs pointerInput

左:clickable  |  右:pointerInput 实现相同点击效果

// clickable 一行搞定
Box(
    modifier = Modifier
        .size(100.dp)
        .background(Color(0xFFFAD7A0))
        .clickable { 
            println("clickable 点击")
        }
)
// pointerInput 原始实现
Box(
    modifier = Modifier
        .size(100.dp)
        .background(Color(0xFFFAD7A0))
        .pointerInput(Unit) {
            detectTapGestures(
                onTap = { 
                    println("pointerInput 点击")
                }
            )
        }
)

detectTapGestures 是 pointerInput 的辅助函数,但依然缺少涟漪与无障碍。若完全手写原始指针,代码更长。

🧩 5. 点击事件体系 · 架构图

Modifier.clickable pointerInput + detectTapGestures (高级封装,仍缺涟漪/无障碍) 原始 指针 ✅ 涟漪 + 无障碍 ✅ 防连点 ✅ 语义点击 ✅ 开箱即用 ⚠️ 部分封装 可监听 tap / doubleTap / longPress 无涟漪,需自行添加 indication 无障碍需手动 semantics 🔧 原始 Down/Move/Up 完全控制 无任何封装

⬆️ 从 clickable 到原始 pointerInput,抽象层级逐渐降低,灵活性升高

🧠 6. 最佳实践 · 何时选择?

  • 优先使用 clickable —— 按钮、列表项、卡片、图标点击,省心且专业。
  • 需要自定义视觉反馈 —— 使用 clickable(interactionSource, indication) 替换涟漪。
  • 需要双击/长按/拖拽 —— 使用 pointerInput + detectTapGestures 或 detectDragGestures。
  • 需要原始事件流 —— 直接使用 awaitPointerEventScope 处理 Down/Move/Up。
  • 无障碍不可忽略 —— clickable 自动支持,pointerInput 需额外添加 .semantics { onClick }。