Touch and input in Jetpack Compose
Comprehensive guide to handling touch and input events in Jetpack Compose, covering focus management, interactions, and pointer input handling.
Contents
Jetpack Compose provides several APIs for handling user input, from simple click interactions to complex gesture recognition. This article explores the different ways to handle touch and input in modern Android applications.
Three Layers of Input Handling
When building interactive user interfaces in Jetpack Compose, there are three main layers to consider:
1. Focus Management
Focus is the first step in input handling. Elements must be able to receive focus before they can process input events. Proper focus management ensures that keyboard input goes to the right component and that navigation flows naturally through your UI.
Key concepts:
- FocusRequester: Programmatically request focus for a specific composable
- FocusDirection: Control navigation direction (Up, Down, Left, Right, Next, Previous)
- onFocusChanged: React when a composable gains or loses focus
2. Interactions
Interactions describe what the user is doing with a component. They include:
- Pressed: User is pressing the element
- Hovered: User’s pointer is over the element
- Focused: Element has keyboard focus
- Dragged: User is dragging the element
- Enabled/Disabled: Component’s interactive state
The Interaction API allows you to detect these states and respond accordingly, enabling visual feedback for user actions.
3. Pointer Input
Pointer input is the lowest level, giving you direct access to raw pointer events (touches, mouse, stylus). This is where you handle complex gestures and fine-grained input control.
Key Input Handling Modifiers
Jetpack Compose provides several modifiers for input handling:
// Simple click handling
Modifier.clickable { onClicked() }
// Semantic button with ripple effect
Modifier.clip(RoundedCornerShape(8.dp))
.background(Color.Blue)
.clickable { onAction() }
// Pointer input for gestures
Modifier.pointerInput(Unit) {
detectTapGestures(
onTap = { offset -> handleTap(offset) },
onLongPress = { offset -> handleLongPress(offset) },
onDoubleTap = { offset -> handleDoubleTap(offset) }
)
}
// Dragging
Modifier.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
handleDrag(dragAmount)
}
}
// Scrolling
Modifier.verticalScroll(rememberScrollState())
Practical Example: Custom Interactive Component
@Composable
fun InteractiveBox(modifier: Modifier = Modifier) {
var isPressed by remember { mutableStateOf(false) }
var offset by remember { mutableStateOf(Offset.Zero) }
Box(
modifier = modifier
.size(100.dp)
.background(if (isPressed) Color.Blue else Color.Gray)
.pointerInput(Unit) {
detectTapGestures(
onPress = { isPressed = true },
onTap = { isPressed = false }
)
detectDragGestures { change, dragAmount ->
offset += dragAmount
}
}
.offset { IntOffset(offset.x.roundToInt(), offset.y.roundToInt()) }
)
}
Best Practices
- Provide Visual Feedback: Always show users when they interact with your components (ripples, color changes, etc.)
- Respect Accessibility: Ensure keyboard navigation works smoothly and screen readers can understand interactive elements
- Test on Real Devices: Touch behavior can differ between emulators and physical devices
- Consider Performance: Complex gesture detection can be expensive; optimize for smooth interactions
Touch and input handling is fundamental to creating responsive, user-friendly Android applications. Mastering these concepts will significantly improve your Compose development skills.