InteractionSource
interface InteractionSource
InteractionSource represents a stream of Interaction
s corresponding to events emitted by a
component. These Interaction
s can be used to change how components appear in different states,
such as when a component is pressed or dragged.
A common use case is androidx.compose.foundation.indication
, where
androidx.compose.foundation.Indication
implementations can subscribe to an InteractionSource
to draw indication for different Interaction
s, such as a ripple effect for
PressInteraction.Press
and a state overlay for DragInteraction.Start
.
For simple cases where you are interested in the binary state of an Interaction
, such as
whether a component is pressed or not, you can use InteractionSource.collectIsPressedAsState
and other extension functions that subscribe and return a Boolean
State
representing whether
the component is in this state or not.
For more complex cases, such as when building an androidx.compose.foundation.Indication
, the
order of the events can change how a component / indication should be drawn. For example, if a
component is being dragged and then becomes focused, the most recent Interaction
is
FocusInteraction.Focus
, so the component should appear in a focused state to signal this event
to the user.
InteractionSource exposes interactions
to support these use cases - a Flow
representing the
stream of all emitted Interaction
s. This also provides more information, such as the press
position of PressInteraction.Press
, so you can show an effect at the specific point the
component was pressed, and whether the press was PressInteraction.Release
or
PressInteraction.Cancel
, for cases when a component should behave differently if the press was
released normally or interrupted by another gesture.
You can collect from interactions
as you would with any other Flow
:
To emit Interaction
s so that consumers can react to them, see MutableInteractionSource
.
Properties
val interactions: Flow<Interaction>
Flow
representing the stream of all Interaction
s emitted through this
InteractionSource
. This can be used to see Interaction
s emitted in order, and with
additional metadata, such as the press position for PressInteraction.Press
.
Code Examples
SimpleInteractionSourceSample
@Composable
fun SimpleInteractionSourceSample() {
// Hoist the MutableInteractionSource that we will provide to interactions
val interactionSource = remember { MutableInteractionSource() }
// Provide the MutableInteractionSource instances to the interactions we want to observe state
// changes for
val draggable =
Modifier.draggable(
interactionSource = interactionSource,
orientation = Orientation.Horizontal,
state = rememberDraggableState { /* update some business state here */ },
)
val clickable =
Modifier.clickable(
interactionSource = interactionSource,
indication = LocalIndication.current,
) { /* update some business state here */
}
// Observe changes to the binary state for these interactions
val isDragged by interactionSource.collectIsDraggedAsState()
val isPressed by interactionSource.collectIsPressedAsState()
// Use the state to change our UI
val (text, color) =
when {
isDragged && isPressed -> "Dragged and pressed" to Color.Red
isDragged -> "Dragged" to Color.Green
isPressed -> "Pressed" to Color.Blue
// Default / baseline state
else -> "Drag me horizontally, or press me!" to Color.Black
}
Box(Modifier.fillMaxSize().wrapContentSize().size(width = 240.dp, height = 80.dp)) {
Box(
Modifier.fillMaxSize()
.then(clickable)
.then(draggable)
.border(BorderStroke(3.dp, color))
.padding(3.dp)
) {
Text(
text,
style = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
modifier = Modifier.fillMaxSize().wrapContentSize(),
)
}
}
}
InteractionSourceFlowSample
@Composable
fun InteractionSourceFlowSample() {
// Hoist the MutableInteractionSource that we will provide to interactions
val interactionSource = remember { MutableInteractionSource() }
// Provide the MutableInteractionSource instances to the interactions we want to observe state
// changes for
val draggable =
Modifier.draggable(
interactionSource = interactionSource,
orientation = Orientation.Horizontal,
state = rememberDraggableState { /* update some business state here */ },
)
val clickable =
Modifier.clickable(
interactionSource = interactionSource,
// This component is a compound component where part of it is clickable and part of it
// is
// draggable. As a result we want to show indication for the _whole_ component, and not
// just for clickable area. We set `null` indication here and provide an explicit
// Modifier.indication instance later that will draw indication for the whole component.
indication = null,
) { /* update some business state here */
}
// SnapshotStateList we will use to track incoming Interactions in the order they are emitted
val interactions = remember { mutableStateListOf<Interaction>() }
// Collect Interactions - if they are new, add them to `interactions`. If they represent stop /
// cancel events for existing Interactions, remove them from `interactions` so it will only
// contain currently active `interactions`.
LaunchedEffect(interactionSource) {
interactionSource.interactions.collect { interaction ->
when (interaction) {
is PressInteraction.Press -> interactions.add(interaction)
is PressInteraction.Release -> interactions.remove(interaction.press)
is PressInteraction.Cancel -> interactions.remove(interaction.press)
is DragInteraction.Start -> interactions.add(interaction)
is DragInteraction.Stop -> interactions.remove(interaction.start)
is DragInteraction.Cancel -> interactions.remove(interaction.start)
}
}
}
// Display some text based on the most recent Interaction stored in `interactions`
val text =
when (interactions.lastOrNull()) {
is DragInteraction.Start -> "Dragged"
is PressInteraction.Press -> "Pressed"
else -> "No state"
}
Column(Modifier.fillMaxSize().wrapContentSize()) {
Row(
// Draw indication for the whole component, based on the Interactions dispatched by
// our hoisted MutableInteractionSource
Modifier.indication(
interactionSource = interactionSource,
indication = LocalIndication.current,
)
) {
Box(
Modifier.size(width = 240.dp, height = 80.dp)
.then(clickable)
.border(BorderStroke(3.dp, Color.Blue))
.padding(3.dp)
) {
val pressed = interactions.any { it is PressInteraction.Press }
Text(
text = if (pressed) "Pressed" else "Not pressed",
style = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
modifier = Modifier.fillMaxSize().wrapContentSize(),
)
}
Box(
Modifier.size(width = 240.dp, height = 80.dp)
.then(draggable)
.border(BorderStroke(3.dp, Color.Red))
.padding(3.dp)
) {
val dragged = interactions.any { it is DragInteraction.Start }
Text(
text = if (dragged) "Dragged" else "Not dragged",
style = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
modifier = Modifier.fillMaxSize().wrapContentSize(),
)
}
}
Text(
text = text,
style = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
modifier = Modifier.fillMaxSize().wrapContentSize(),
)
}
}