Muchos elementos componibles tienen compatibilidad integrada con toques o clics y, además, incluyen una lambda onClick
. Por ejemplo, puedes crear un Surface
en el que se pueda hacer clic que incluya todo el comportamiento de Material Design adecuado para la interacción con las plataformas:
Surface(onClick = { /* handle click */ }) { Text("Click me!", Modifier.padding(24.dp)) }
Sin embargo, los clics no son la única forma en que un usuario puede interactuar con los elementos componibles. En esta página, se enfocan los gestos que involucran un solo puntero, en los que la posición de ese puntero no es significativa para el control de ese evento. En la siguiente tabla, se enumeran estos tipos de gestos:
Gesto |
Descripción |
Presiona (o haz clic) |
El puntero baja y sube |
Presionar dos veces |
El puntero baja, sube, baja y sube. |
Mantener presionado |
El puntero baja y se mantiene durante más tiempo |
Prensa |
El puntero baja |
Cómo responder a una presión o un clic
clickable
es un modificador de uso general que hace que un elemento componible reaccione a las presiones o los clics. Este modificador también agrega funciones adicionales, como compatibilidad con el enfoque, el desplazamiento del mouse y la pluma stylus, y una indicación visual personalizable cuando se presiona. El modificador responde a los "clics" en el sentido más amplio de la palabra, no solo con el mouse o el dedo, sino también con eventos de clic a través de la entrada del teclado o cuando se usan servicios de accesibilidad.
Imagina una cuadrícula de imágenes, en la que una imagen se muestra en pantalla completa cuando un usuario hace clic en ella:
Puedes agregar el modificador clickable
a cada elemento de la cuadrícula para implementar este comportamiento:
@Composable private fun ImageGrid(photos: List<Photo>) { var activePhotoId by rememberSaveable { mutableStateOf<Int?>(null) } LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) { items(photos, { it.id }) { photo -> ImageItem( photo, Modifier.clickable { activePhotoId = photo.id } ) } } if (activePhotoId != null) { FullScreenImage( photo = photos.first { it.id == activePhotoId }, onDismiss = { activePhotoId = null } ) } }
El modificador clickable
también agrega un comportamiento adicional:
interactionSource
yindication
, que dibujan una onda de forma predeterminada cuando un usuario presiona el elemento componible. Obtén información para personalizarlos en la página Cómo controlar las interacciones del usuario.- Permite que los servicios de accesibilidad interactúen con el elemento configurando la información semántica.
- Admite la interacción con el teclado o el joystick, ya que permite enfocar y presionar
Enter
o el centro del pad direccional para interactuar. - Haz que el elemento sea desplazable para que responda cuando el mouse o la pluma stylus se coloquen sobre él.
Mantener presionado para mostrar un menú contextual
combinedClickable
te permite agregar el comportamiento de presionar dos veces o mantener presionado, además del comportamiento de clic normal. Puedes usar combinedClickable
para mostrar un menú contextual cuando un usuario presiona y mantiene presionada una imagen de la cuadrícula:
var contextMenuPhotoId by rememberSaveable { mutableStateOf<Int?>(null) } val haptics = LocalHapticFeedback.current LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) { items(photos, { it.id }) { photo -> ImageItem( photo, Modifier .combinedClickable( onClick = { activePhotoId = photo.id }, onLongClick = { haptics.performHapticFeedback(HapticFeedbackType.LongPress) contextMenuPhotoId = photo.id }, onLongClickLabel = stringResource(R.string.open_context_menu) ) ) } } if (contextMenuPhotoId != null) { PhotoActionsSheet( photo = photos.first { it.id == contextMenuPhotoId }, onDismissSheet = { contextMenuPhotoId = null } ) }
Como práctica recomendada, debes incluir comentarios táctiles cuando el usuario mantenga presionados los elementos, por lo que el fragmento incluye la invocación a performHapticFeedback
.
Presiona una pantalla para descartar un elemento componible
En los ejemplos anteriores, clickable
y combinedClickable
agregan funcionalidad útil a tus elementos componibles. Muestran una indicación visual sobre la interacción, responden al desplazamiento del mouse y son compatibles con el enfoque, el teclado y la accesibilidad. Sin embargo, este comportamiento adicional no siempre es deseable.
Veamos la pantalla de detalles de la imagen. El fondo debe ser semitransparente y el usuario debe poder presionarlo para descartar la pantalla de detalles:
En este caso, ese fondo no debe tener ninguna indicación visual sobre la interacción, no debe responder al desplazamiento del mouse, no debe poder enfocarse y su respuesta a los eventos del teclado y de accesibilidad difiere de la de un elemento componible típico. En lugar de intentar adaptar el comportamiento de clickable
, puedes bajar a un nivel de abstracción inferior y usar directamente el modificador pointerInput
en combinación con el método detectTapGestures
:
@Composable private fun Scrim(onClose: () -> Unit, modifier: Modifier = Modifier) { val strClose = stringResource(R.string.close) Box( modifier // handle pointer input .pointerInput(onClose) { detectTapGestures { onClose() } } // handle accessibility services .semantics(mergeDescendants = true) { contentDescription = strClose onClick { onClose() true } } // handle physical keyboard input .onKeyEvent { if (it.key == Key.Escape) { onClose() true } else { false } } // draw scrim .background(Color.DarkGray.copy(alpha = 0.75f)) ) }
Como clave del modificador pointerInput
, pasas la lambda onClose
. Esto vuelve a ejecutar la lambda automáticamente, lo que garantiza que se llame a la devolución de llamada correcta cuando el usuario presione la pantalla en blanco.
Presiona dos veces para acercar
A veces, clickable
y combinedClickable
no incluyen suficiente información para responder a la interacción de la manera correcta. Por ejemplo, los elementos componibles pueden necesitar acceso a la posición dentro de los límites del elemento componible donde se produjo la interacción.
Veamos nuevamente la pantalla de detalles de la imagen. Una práctica recomendada es permitir acercar la imagen con un doble toque:
Como puedes ver en el video, el zoom se acerca alrededor de la posición del evento de pulsación. El resultado es diferente cuando acercamos la parte izquierda de la imagen en comparación con la parte derecha. Podemos usar el modificador pointerInput
en combinación con detectTapGestures
para incorporar la posición de la presión en nuestro cálculo:
var zoomed by remember { mutableStateOf(false) } var zoomOffset by remember { mutableStateOf(Offset.Zero) } Image( painter = rememberAsyncImagePainter(model = photo.highResUrl), contentDescription = null, modifier = modifier .pointerInput(Unit) { detectTapGestures( onDoubleTap = { tapOffset -> zoomOffset = if (zoomed) Offset.Zero else calculateOffset(tapOffset, size) zoomed = !zoomed } ) } .graphicsLayer { scaleX = if (zoomed) 2f else 1f scaleY = if (zoomed) 2f else 1f translationX = zoomOffset.x translationY = zoomOffset.y } )
Recomendaciones para ti
- Nota: El texto del vínculo se muestra cuando JavaScript está desactivado
- Información sobre los gestos
- Material Design 2 en Compose
- Kotlin para Jetpack Compose