Created
December 5, 2023 18:38
-
-
Save luboganev/2b7e32c287d323f5d98e5fb21652bc15 to your computer and use it in GitHub Desktop.
Jetpack Compose custom focus and other interaction states
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A convenience wrapper around the [Interaction] types | |
*/ | |
@Immutable | |
data class CustomInteraction( | |
val enabled: Boolean = false, | |
val hovered: Boolean = false, | |
val pressed: Boolean = false, | |
val focused: Boolean = false, | |
) | |
/** | |
* A custom handler for [MutableInteractionSource], which supports custom visualizing | |
* pressed, focused, hover and enabled states of an interactive UI component. | |
*/ | |
@Composable | |
fun rememberCustomInteractionState( | |
enabled: Boolean, | |
interactionSource: MutableInteractionSource, | |
): State<CustomInteraction> { | |
val customInteractionState = remember { mutableStateOf(CustomInteraction()) } | |
val interactions = remember { mutableStateListOf<Interaction>() } | |
LaunchedEffect(interactionSource) { | |
interactionSource.interactions.collect { interaction -> | |
when (interaction) { | |
is HoverInteraction.Enter -> { | |
interactions.add(interaction) | |
} | |
is HoverInteraction.Exit -> { | |
interactions.remove(interaction.enter) | |
} | |
is FocusInteraction.Focus -> { | |
interactions.add(interaction) | |
} | |
is FocusInteraction.Unfocus -> { | |
interactions.remove(interaction.focus) | |
} | |
is PressInteraction.Press -> { | |
interactions.add(interaction) | |
} | |
is PressInteraction.Release -> { | |
delay(KEEP_PRESSED_STATE_DELAY_MILLIS) | |
interactions.remove(interaction.press) | |
} | |
is PressInteraction.Cancel -> { | |
delay(KEEP_PRESSED_STATE_DELAY_MILLIS) | |
interactions.remove(interaction.press) | |
} | |
} | |
} | |
} | |
if (!enabled) { | |
customInteractionState.value = CustomInteraction() | |
} else { | |
val isFocused = interactions.any { it is FocusInteraction.Focus } | |
val isPressed = interactions.any { it is PressInteraction.Press } | |
val isHovered = interactions.any { it is HoverInteraction.Enter } | |
customInteractionState.value = CustomInteraction( | |
enabled = true, | |
hovered = isHovered, | |
pressed = isPressed, | |
focused = isFocused, | |
) | |
} | |
return customInteractionState | |
} | |
// Without this, the change of state when just | |
// tapping is so fast, you can't even see a visual change in the UI. | |
// Remember, the ripple is an animation that starts when you tap but runs also | |
// after you've tapped, while simple color changes are instant. | |
private const val KEEP_PRESSED_STATE_DELAY_MILLIS = 100L |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
val interactionSource = remember { MutableInteractionSource() } | |
val customInteractionState by rememberCustomInteractionState(enabled = true, interactionSource = interactionSource) | |
val color by remember { | |
derivedStateOf { | |
if (customInteractionState.focused) Color.Blue else Color.Green | |
} | |
} | |
Box( | |
modifier = Modifier | |
.background(color) | |
.clickable( | |
interactionSource = interactionSource, | |
indication = null, // That one's important to prevent the default ripple | |
onClick = {}, | |
) | |
) { | |
Text( | |
text = "Some content", | |
style = MaterialTheme.typography.h1 | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment