Tocar y presionar

Muchos elementos componibles tienen compatibilidad integrada con toques o clics, y también incluyen un Lambda es onClick. Por ejemplo, puedes crear un elemento Surface en el que se puede hacer clic y que incluye todo el comportamiento de Material Design apropiado 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 esos elementos. Esta página se centra en los gestos que implican un solo puntero, donde la posición del Ese puntero no es significativo para el manejo de ese evento. Lo siguiente En la tabla, se enumeran estos tipos de gestos:

Gesto

Descripción

Presionar (o hacer clic)

El puntero baja y, luego, sube.

Presionar dos veces

El puntero baja, sube, baja, sube

Mantener presionado

El puntero baja y permanece retenido por más tiempo.

Prensa

El puntero baja

Responde a tocar o hacer clic

clickable es un modificador de uso general que hace que un elemento componible reaccione a presiones o clics. Este modificador también agrega funciones adicionales, como compatibilidad con el enfoque, el mouse y la pluma stylus, y una indicación visual personalizable cuando pulsado. 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 hacer clic en eventos a través de la entrada del teclado o cuando usando 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 él:

Puedes agregar el modificador clickable a cada elemento de la cuadrícula para implementar esto. 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 y indication, que dibujan un ripple de forma predeterminada cuando se el usuario presiona el elemento componible. Para obtener más información sobre cómo personalizar estas opciones, consulte la página Administración de usuarios interacciones.
  • Permite que los servicios de accesibilidad interactúen con el elemento estableciendo la información semántica.
  • Admite la interacción con teclado o joystick, ya que permite enfocar y presionar Enter o el centro del pad direccional para interactuar
  • Hacer que el elemento se pueda colocar sobre el elemento para que responda cuando se coloca el cursor sobre el mouse o la pluma stylus por encima.

Mantén presionado para mostrar un menú contextual contextual

combinedClickable te permite agregar un comportamiento de presionar dos veces o mantener presionado en además del comportamiento normal de clics. Puedes usar combinedClickable para mostrar un Menú contextual cuando un usuario toca y mantiene 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 respuesta táctil cuando el usuario mantiene presionados los elementos, por lo que el fragmento incluye los performHapticFeedback.

Presiona una lámina para descartar un elemento componible

En los ejemplos anteriores, clickable y combinedClickable agregan información útil a tus elementos componibles. Muestran una indicación visual sobre la interacción, responden a la acción de colocar el cursor sobre ellos e incluyen compatibilidad con enfoque, teclado y 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 debería poder presionar ese fondo para descartar la pantalla de detalles:

En este caso, ese fondo no debe tener ninguna indicación visual en interactiva, no debe responder a la acción de colocar el cursor sobre un elemento, no debe ser enfocable y su la respuesta a los eventos de accesibilidad y de teclado difiere de la de una componible. En lugar de tratar de adaptar el comportamiento de clickable, puedes descartar hasta 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. Esta vuelve a ejecutar automáticamente la expresión lambda y se asegura de que se llame a la devolución de llamada correcta. cuando el usuario presiona la lámina.

Presiona dos veces para acercar

A veces, clickable y combinedClickable no incluyen suficiente información responder a la interacción de la manera correcta. Por ejemplo, los elementos componibles podrían necesitan acceso a la posición dentro de los límites del elemento componible donde la interacción tuvo lugar.

Veamos la pantalla de detalles de la imagen de nuevo. Una práctica recomendada es posible acercar la imagen tocando dos veces:

Como puedes ver en el video, el acercamiento se produce alrededor de la posición en la que se presionaba para cada evento. 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 del toque en nuestra 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
        }
)