Muchos elementos componibles tienen compatibilidad integrada con presiones o clics, e incluyen una expresión lambda onClick
. Por ejemplo, puedes crear un elemento Surface
en el que se pueda hacer clic que incluya todo el comportamiento de Material Design adecuado para la interacción con 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. Esta página se enfoca en los gestos que involucran un solo puntero, donde 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 |
Description |
Presiona (o haz clic) |
El puntero baja y, luego, hacia arriba |
Presionar dos veces |
El puntero va hacia abajo, arriba, abajo, arriba |
mantener presionado |
El puntero deja de funcionar y se mantiene presionado durante más tiempo |
Prensa |
El puntero cae |
Responder a presiones o clics
clickable
es un modificador de uso general que hace que un elemento componible reaccione a los toques o 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 "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 mediante 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 comportamiento adicional:
interactionSource
yindication
, que dibujan una onda de forma predeterminada cuando un usuario presiona el elemento componible. Descubre cómo 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 el enfoque y presiona
Enter
, o el centro del pad direccional, para interactuar. - Permite que se pueda colocar el cursor sobre el elemento, de modo que responda al mouse o la pluma stylus que se coloca sobre él.
Mantén presionado para mostrar un menú contextual contextual
combinedClickable
te permite agregar un comportamiento de presionar dos veces o mantener presionado, además del comportamiento normal de clics. Puedes usar combinedClickable
para mostrar un menú contextual cuando un usuario toque y mantenga presionada una imagen de 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 respuestas táctiles cuando el usuario mantiene presionados los elementos, por lo que el fragmento incluye la invocación performHapticFeedback
.
Presiona una lámina para descartar un elemento componible
En los ejemplos anteriores, clickable
y combinedClickable
agregan funcionalidades útiles a tus elementos componibles. Muestran una indicación visual sobre la interacción, responden al desplazamiento del cursor y, además, incluyen compatibilidad con el enfoque, el teclado y la accesibilidad. Sin embargo, este comportamiento adicional no siempre es conveniente.
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, el fondo no debe tener ninguna indicación visual sobre la interacción, no debe responder al colocar el cursor sobre un elemento, no debe ser enfocable y su respuesta a los eventos de teclado y accesibilidad difiere de la de un elemento componible típico. En lugar de intentar adaptar el comportamiento de clickable
, puedes desplazarte a un nivel de abstracción más bajo y usar directamente el modificador pointerInput
en combinación con el método detectTapGestures
:
@OptIn(ExperimentalComposeUiApi::class) @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 tecla del modificador pointerInput
, pasas la lambda onClose
. De esta manera, se volverá a ejecutar la expresión lambda automáticamente y se asegurará de que se llame a la devolución de llamada correcta cuando el usuario presione la lámina.
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, es posible que los elementos componibles necesiten acceso a la posición dentro de los límites del elemento donde se llevó a cabo la interacción.
Veamos la pantalla de detalles de la imagen de nuevo. Una práctica recomendada es hacer que sea posible acercar la imagen presionando dos veces:
Como se puede ver en el video, el acercamiento se produce alrededor de la posición del evento de toque. El resultado es diferente cuando acercamos la imagen en la parte izquierda en comparación con la parte derecha. Podemos usar el modificador pointerInput
junto con el detectTapGestures
para incorporar la posición de toque 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