点击、InteractionSource 与 Indication · AndroidX 源码指南
AAndroidX 源码指南
Compose Foundation
Compose 底层 · androidx.compose.foundation

点击、InteractionSource 与 Indication1.13.0-alpha01

一个可点击组件如何同时表达行为、状态和视觉反馈。

最后更新 2026-08-01

clickable 已经组合了多层能力

Modifier.clickable(
    enabled = enabled,
    role = Role.Button,
    onClick = onClick,
)

它不只是监听 pointer,还处理:

  • 焦点(键盘可达)
  • 语义(TalkBack 读出 Button 角色)
  • indication(涟漪反馈)
  • 最小点击面积minimumInteractiveComponentSize

普通点击区域优先用 clickable,而不是 pointerInput { detectTapGestures(...) }——后者丢掉语义、焦点和 indication。

观察交互状态

val interactions = remember { MutableInteractionSource() }
val pressed by interactions.collectIsPressedAsState()

Box(
    Modifier
        .clickable(
            interactionSource = interactions,
            indication = LocalIndication.current,
            onClick = onClick,
        )
        .background(if (pressed) PressedColor else NormalColor)
) {
    Content()
}

InteractionSource 发出事件,扩展函数配对成状态:

扩展状态
collectIsPressedAsState()是否按住
collectIsFocusedAsState()是否聚焦
collectIsHoveredAsState()是否悬停(桌面)
collectIsDraggedAsState()是否拖拽中

关键:组件与外部要观察同一交互,必须共享同一个 source——组件内部新建的 source 外部拿不到。

Indication 只负责视觉

// 自定义指示器(示例:纯色高亮而非涟漪)
class HighlightIndication : Indication {
    override fun rememberUpdatedInstance(
        interactionSource: InteractionSource,
    ): IndicationInstance = HighlightInstance(interactionSource)
}

class HighlightInstance(
    private val source: InteractionSource,
) : IndicationInstance {
    private val pressedColor = Color(0x22000000)

    override fun ContentDrawScope.drawIndication() {
        drawContent()
        val pressed by source.collectIsPressedAsState()
        if (pressed) drawRect(pressedColor)
    }
}
  • Indication 读取交互并绘制反馈,不负责 onClick
  • 自定义视觉反馈时保留 clickable 的行为和语义,不要退回原始 pointer handler。
  • 主题的 indication 通过 LocalIndication 提供。

常见陷阱

  • 用 pointerInput 代替 clickable:丢语义/焦点/indication。
  • source 不共享:组件内部监听,外部再建一个——两个 source 互不通知。
  • Indication 里触发逻辑:Indication 只画,不处理点击。
  • 忘记 enabled 联动enabled=false 时应该没有 pressed 状态。

复制即用

@Composable
fun PressableCard(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit,
) {
    val interactions = remember { MutableInteractionSource() }
    val pressed by interactions.collectIsPressedAsState()

    Box(
        modifier
            .clip(RoundedCornerShape(16.dp))
            .scale(if (pressed) 0.97f else 1f)
            .clickable(
                interactionSource = interactions,
                indication = null,   // 自定义反馈时关掉默认涟漪
                onClick = onClick,
            ),
        contentAlignment = Alignment.Center,
    ) {
        content()
    }
}

要点

  • clickable = pointer + 焦点 + 语义 + indication,别退回原始手势。
  • InteractionSource 发出事件,collect*AsState 配对成状态;共享 source 才能互见。
  • Indication 只负责视觉绘制;自定义反馈保留语义。
  • 状态驱动(pressed → 缩放/变色)是最常见的自定义反馈模式。

相关页面