Tools
Tools: Jetpack Compose Gestures — Tap, Swipe, Drag Patterns
2026-03-02
0 views
admin
Jetpack Compose Gestures — Tap, Swipe, Drag Patterns ## 1. Clickable — Simple Tap Detection ## 2. CombinedClickable — Long Press + Double Tap ## 3. SwipeToDismissBox — Swipe to Remove ## 4. Draggable — Single-Direction Movement ## 5. PointerInput + DetectDragGestures — Multi-Direction & Custom Logic ## 6. Pinch Zoom with DetectTransformGestures ## Pattern Selection Quick Reference ## Performance Tips ## Next Steps Building interactive Android apps requires responsive gesture handling. Jetpack Compose provides multiple APIs for different interaction patterns. Here's a practical guide. For basic button interactions: For custom clickable surfaces without button styling: Handle multiple tap patterns on the same element: Use cases: Context menus on long press, favorite toggle on double tap. Built-in support for dismissible list items: Constrain dragging to horizontal or vertical axis: Common use: Slider controls, horizontal scrollers. For complex drag patterns requiring offset tracking and custom constraints: Key pattern: change.consume() prevents propagation to parent composables. Multi-touch scaling, rotation, and pan detection: Use cases: Photo galleries, map viewers, design tools. Learn more about advanced pointer events and multi-gesture combination patterns in the official Compose documentation. Build interactive experiences that delight your users. 8 Android App Templates — Production-ready Kotlin + Compose apps ready to customize and ship to Google Play. → https://myougatheax.gumroad.com Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to ? It will become hidden in your post, but will still be visible via the comment's permalink. as well , this person and/or CODE_BLOCK: Button( onClick = { /* handle click */ }, modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text("Tap Me") } CODE_BLOCK: Button( onClick = { /* handle click */ }, modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text("Tap Me") } CODE_BLOCK: Button( onClick = { /* handle click */ }, modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text("Tap Me") } CODE_BLOCK: Text( "Click here", modifier = Modifier .clickable { /* action */ } .padding(16.dp) ) CODE_BLOCK: Text( "Click here", modifier = Modifier .clickable { /* action */ } .padding(16.dp) ) CODE_BLOCK: Text( "Click here", modifier = Modifier .clickable { /* action */ } .padding(16.dp) ) CODE_BLOCK: Text( "Try long press or double tap", modifier = Modifier .combinedClickable( onClick = { println("Clicked") }, onDoubleClick = { println("Double tapped") }, onLongClick = { println("Long pressed") } ) .padding(16.dp) ) CODE_BLOCK: Text( "Try long press or double tap", modifier = Modifier .combinedClickable( onClick = { println("Clicked") }, onDoubleClick = { println("Double tapped") }, onLongClick = { println("Long pressed") } ) .padding(16.dp) ) CODE_BLOCK: Text( "Try long press or double tap", modifier = Modifier .combinedClickable( onClick = { println("Clicked") }, onDoubleClick = { println("Double tapped") }, onLongClick = { println("Long pressed") } ) .padding(16.dp) ) CODE_BLOCK: var isDismissed by remember { mutableStateOf(false) } if (!isDismissed) { SwipeToDismissBox( modifier = Modifier.fillMaxWidth(), onDismissed = { isDismissed = true }, backgroundContent = { Box( Modifier .fillMaxSize() .background(Color.Red), contentAlignment = Alignment.CenterEnd ) { Icon(Icons.Default.Delete, contentDescription = null) } } ) { ListItem(headlineContent = { Text("Swipe to dismiss") }) } } CODE_BLOCK: var isDismissed by remember { mutableStateOf(false) } if (!isDismissed) { SwipeToDismissBox( modifier = Modifier.fillMaxWidth(), onDismissed = { isDismissed = true }, backgroundContent = { Box( Modifier .fillMaxSize() .background(Color.Red), contentAlignment = Alignment.CenterEnd ) { Icon(Icons.Default.Delete, contentDescription = null) } } ) { ListItem(headlineContent = { Text("Swipe to dismiss") }) } } CODE_BLOCK: var isDismissed by remember { mutableStateOf(false) } if (!isDismissed) { SwipeToDismissBox( modifier = Modifier.fillMaxWidth(), onDismissed = { isDismissed = true }, backgroundContent = { Box( Modifier .fillMaxSize() .background(Color.Red), contentAlignment = Alignment.CenterEnd ) { Icon(Icons.Default.Delete, contentDescription = null) } } ) { ListItem(headlineContent = { Text("Swipe to dismiss") }) } } CODE_BLOCK: var offsetX by remember { mutableStateOf(0f) } Box( modifier = Modifier .offset { IntOffset(offsetX.roundToInt(), 0) } .draggable( state = rememberDraggableState { delta -> offsetX += delta }, orientation = Orientation.Horizontal ) .size(100.dp) .background(Color.Blue) ) CODE_BLOCK: var offsetX by remember { mutableStateOf(0f) } Box( modifier = Modifier .offset { IntOffset(offsetX.roundToInt(), 0) } .draggable( state = rememberDraggableState { delta -> offsetX += delta }, orientation = Orientation.Horizontal ) .size(100.dp) .background(Color.Blue) ) CODE_BLOCK: var offsetX by remember { mutableStateOf(0f) } Box( modifier = Modifier .offset { IntOffset(offsetX.roundToInt(), 0) } .draggable( state = rememberDraggableState { delta -> offsetX += delta }, orientation = Orientation.Horizontal ) .size(100.dp) .background(Color.Blue) ) CODE_BLOCK: var position by remember { mutableStateOf(Offset(0f, 0f)) } Box( modifier = Modifier .fillMaxSize() .pointerInput(Unit) { detectDragGestures( onDrag = { change, offset -> change.consume() position += offset }, onDragEnd = { /* snap to grid, for example */ }, onDragCancel = { /* reset */ } ) } ) { Box( modifier = Modifier .offset { IntOffset(position.x.toInt(), position.y.toInt()) } .size(80.dp) .background(Color.Green) ) } CODE_BLOCK: var position by remember { mutableStateOf(Offset(0f, 0f)) } Box( modifier = Modifier .fillMaxSize() .pointerInput(Unit) { detectDragGestures( onDrag = { change, offset -> change.consume() position += offset }, onDragEnd = { /* snap to grid, for example */ }, onDragCancel = { /* reset */ } ) } ) { Box( modifier = Modifier .offset { IntOffset(position.x.toInt(), position.y.toInt()) } .size(80.dp) .background(Color.Green) ) } CODE_BLOCK: var position by remember { mutableStateOf(Offset(0f, 0f)) } Box( modifier = Modifier .fillMaxSize() .pointerInput(Unit) { detectDragGestures( onDrag = { change, offset -> change.consume() position += offset }, onDragEnd = { /* snap to grid, for example */ }, onDragCancel = { /* reset */ } ) } ) { Box( modifier = Modifier .offset { IntOffset(position.x.toInt(), position.y.toInt()) } .size(80.dp) .background(Color.Green) ) } CODE_BLOCK: var scale by remember { mutableStateOf(1f) } var offset by remember { mutableStateOf(Offset(0f, 0f)) } Image( painter = painterResource(id = R.drawable.sample), contentDescription = null, modifier = Modifier .fillMaxSize() .pointerInput(Unit) { detectTransformGestures { centroid, pan, zoom, rotation -> scale *= zoom offset += pan } } .graphicsLayer( scaleX = scale, scaleY = scale, translationX = offset.x, translationY = offset.y ) ) CODE_BLOCK: var scale by remember { mutableStateOf(1f) } var offset by remember { mutableStateOf(Offset(0f, 0f)) } Image( painter = painterResource(id = R.drawable.sample), contentDescription = null, modifier = Modifier .fillMaxSize() .pointerInput(Unit) { detectTransformGestures { centroid, pan, zoom, rotation -> scale *= zoom offset += pan } } .graphicsLayer( scaleX = scale, scaleY = scale, translationX = offset.x, translationY = offset.y ) ) CODE_BLOCK: var scale by remember { mutableStateOf(1f) } var offset by remember { mutableStateOf(Offset(0f, 0f)) } Image( painter = painterResource(id = R.drawable.sample), contentDescription = null, modifier = Modifier .fillMaxSize() .pointerInput(Unit) { detectTransformGestures { centroid, pan, zoom, rotation -> scale *= zoom offset += pan } } .graphicsLayer( scaleX = scale, scaleY = scale, translationX = offset.x, translationY = offset.y ) ) - Use rememberDraggableState to preserve state across recompositions - Call change.consume() in pointerInput to prevent gesture propagation - Avoid complex layout calculations during gesture callbacks - Test with ComposeTestRule and performTouchInput() for automated testing
toolsutilitiessecurity toolsjetpackcomposegesturesswipepatterns