Muchos elementos componibles tienen compatibilidad integrada con presiones o clics, y también incluyen una lambda onClick. Por ejemplo, puedes crear un Surface en el que se pueda hacer clic y que incluya todo el comportamiento de Material Design apropiado para la interacción con las superficies:
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, en los que la posición de ese puntero no es significativa para el manejo de ese evento. En la siguiente tabla, se enumeran estos tipos de gestos:
Gesto |
Descripción |
Presiona (o haz clic) |
El puntero baja y, luego, sube |
Presionar dos veces |
El puntero baja, sube, baja y sube. |
Mantener presionado |
El puntero baja y se mantiene presionado durante más tiempo |
Prensa |
El puntero baja |
Cómo responder cuando se presiona o se hace clic
clickable es un modificador de uso frecuente que hace que un elemento componible reaccione a presiones 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 los "clics" en el sentido más amplio de la palabra, no solo con el mouse o el dedo, sino también con los eventos de clic a través de la entrada del teclado o cuando se usan los 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:
interactionSourceyindication, que dibujan una onda de forma predeterminada cuando un usuario presiona el elemento que admite composición. 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 de semántica.
- Admite la interacción con el teclado o el joystick, ya que permite enfocar y presionar
Entero el centro del pad direccional para interactuar. - Haz que el elemento sea desplazable, de modo que responda al mouse o a la pluma stylus que se desplaza sobre él.
Mantén presionado para mostrar un menú contextual
combinedClickable te permite agregar un comportamiento de presión doble o prolongada, además del comportamiento de clic normal. Puedes usar combinedClickable para mostrar un menú contextual cuando un usuario 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 respuesta táctil cuando el usuario mantenga presionados los elementos, por lo que el fragmento incluye la invocación de performHapticFeedback.
Cómo descartar un elemento componible presionando una pantalla de bloqueo
En los ejemplos anteriores, clickable y combinedClickable agregan funciones útiles a tus elementos de componibilidad. Muestran una indicación visual en la interacción, responden al desplazamiento y, además, incluyen compatibilidad 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 en la interacción, no debe responder al desplazamiento, no debe poder enfocarse 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 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 expresión lambda onClose. Esto vuelve a ejecutar automáticamente la expresión lambda, lo que garantiza que se llame a la devolución de llamada correcta cuando el usuario presione la máscara.
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 que admiten composición necesiten acceso a la posición dentro de los límites del elemento que admite composición en el que se produjo la interacción.
Volvamos a mirar la pantalla de detalles de la imagen. Una práctica recomendada es permitir que se acerque la imagen presionando dos veces:
Como puedes ver en el video, el acercamiento se produce alrededor de la posición del evento de presió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