Gestos

Compose proporciona una variedad de API para ayudarte a detectar gestos que se generan a partir de las interacciones de los usuarios. Las API abarcan una gran variedad de casos de uso:

  • Algunos son de alto nivel y están diseñados para cubrir los gestos más utilizados. Por ejemplo, el modificador clickable permite la detección sencilla de un clic y, además, proporciona funciones de accesibilidad y muestra indicadores visuales cuando ante toques (como ondas).

  • También hay detectores de gestos menos usados que ofrecen más flexibilidad en un nivel más bajo, como PointerInputScope.detectTapGestures o PointerInputScope.detectDragGestures, pero no incluyas las funciones adicionales.

Toques

El modificador clickable permite que las apps detecten clics en el elemento al que se aplica.

@Composable
fun ClickableSample() {
    val count = remember { mutableStateOf(0) }
    // content that you want to make clickable
    Text(
        text = count.value.toString(),
        modifier = Modifier.clickable { count.value += 1 }
    )
}

Ejemplo de un elemento de IU que responde a toques

Cuando se necesita más flexibilidad, puedes proporcionar un detector de gestos de toque a través del modificador pointerInput:

Modifier.pointerInput(Unit) {
    detectTapGestures(
        onPress = { /* Called when the gesture starts */ },
        onDoubleTap = { /* Called on Double Tap */ },
        onLongPress = { /* Called on Long Press */ },
        onTap = { /* Called on Tap */ }
    )
}

Desplazamiento

Modificadores de desplazamiento

Los selectores verticalScroll y horizontalScroll proporcionan la forma más sencilla de permitir que el usuario se desplace por un elemento cuando los límites del contenido sean más grandes que las restricciones de tamaño máximo. Con los modificadores verticalScroll y horizontalScroll, no necesitas traducir ni compensar el contenido.

@Composable
fun ScrollBoxes() {
    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .verticalScroll(rememberScrollState())
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Una lista vertical simple que responde a los gestos de desplazamiento

ScrollState te permite cambiar la posición del desplazamiento u obtener su estado actual. Para crearlo con los parámetros predeterminados, usa rememberScrollState().

@Composable
private fun ScrollBoxesSmooth() {

    // Smoothly scroll 100px on first composition
    val state = rememberScrollState()
    LaunchedEffect(Unit) { state.animateScrollTo(100) }

    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .padding(horizontal = 8.dp)
            .verticalScroll(state)
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Modificador desplazable

El modificador scrollable difiere de los modificadores de desplazamiento porque scrollable detecta los gestos de desplazamiento, pero no desplaza su contenido. Se necesita un ScrollableController para que este modificador funcione correctamente. Cuando construyas ScrollableController, debes proporcionar una función consumeScrollDelta que se invoque en cada paso de desplazamiento (con entrada de gestos, desplazamiento suave o arrastrar y soltar) con el delta en píxeles. La distancia de desplazamiento consumida debe mostrarse desde esta función para garantizar la propagación adecuada de los eventos.

En el siguiente fragmento, se detectan los gestos y se muestra un valor numérico para un desplazamiento, pero no se desplaza ningún elemento:

@Composable
fun ScrollableSample() {
    // actual composable state
    var offset by remember { mutableStateOf(0f) }
    Box(
        Modifier
            .size(150.dp)
            .scrollable(
                orientation = Orientation.Vertical,
                // Scrollable state: describes how to consume
                // scrolling delta and update offset
                state = rememberScrollableState { delta ->
                    offset += delta
                    delta
                }
            )
            .background(Color.LightGray),
        contentAlignment = Alignment.Center
    ) {
        Text(offset.toString())
    }
}

Un elemento de IU que detecta la presión del dedo y muestra el valor numérico de la ubicación del dedo

Desplazamiento anidado

Compose admite desplazamiento anidado, en el que múltiples elementos reaccionan a un solo gesto de desplazamiento. Un ejemplo típico de desplazamiento anidado es una lista dentro de otra, y un caso más complejo es una barra de herramientas que se puede contraer.

Desplazamiento anidado automático

El desplazamiento anidado simple no requiere ninguna acción de tu parte. Los gestos que inician una acción de desplazamiento se propagan de elementos secundarios a superiores de forma automática, de modo que cuando el elemento secundario no puede desplazarse más, se controla el gesto con el elemento superior.

En el siguiente ejemplo, se muestran los elementos con un modificador verticalScroll aplicado dentro de un contenedor que también tiene un modificador verticalScroll aplicado.

val gradient = Brush.verticalGradient(0f to Color.Gray, 1000f to Color.White)
Box(
    modifier = Modifier
        .background(Color.LightGray)
        .verticalScroll(rememberScrollState())
        .padding(32.dp)
) {
    Column {
        repeat(6) {
            Box(
                modifier = Modifier
                    .height(128.dp)
                    .verticalScroll(rememberScrollState())
            ) {
                Text(
                    "Scroll here",
                    modifier = Modifier
                        .border(12.dp, Color.DarkGray)
                        .background(brush = gradient)
                        .padding(24.dp)
                        .height(150.dp)
                )
            }
        }
    }
}

Dos elementos de la IU de desplazamiento vertical anidados que responden a gestos dentro y fuera del elemento interno

Cómo usar el modificador nestedScroll

Si necesitas crear un desplazamiento coordinado avanzado entre varios elementos, el modificador nestedScroll te brinda más flexibilidad definiendo una jerarquía de desplazamiento anidada.

Arrastres

El modificador draggable es el punto de entrada de alto nivel para arrastrar gestos en una sola orientación e informa la distancia de arrastre en píxeles.

Es importante tener en cuenta que este modificador es similar a scrollable, ya que solo detecta el gesto. Debes conservar el estado y representarlo en la pantalla, por ejemplo, para mover el elemento mediante el modificador offset:

var offsetX by remember { mutableStateOf(0f) }
Text(
    modifier = Modifier
        .offset { IntOffset(offsetX.roundToInt(), 0) }
        .draggable(
            orientation = Orientation.Horizontal,
            state = rememberDraggableState { delta ->
                offsetX += delta
            }
        ),
    text = "Drag me!"
)

Si necesitas controlar todo el gesto de arrastre, procura usar el detector de gestos de arrastre en su lugar, a través del modificador pointerInput.

Box(modifier = Modifier.fillMaxSize()) {
    var offsetX by remember { mutableStateOf(0f) }
    var offsetY by remember { mutableStateOf(0f) }

    Box(
        Modifier
            .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
            .background(Color.Blue)
            .size(50.dp)
            .pointerInput(Unit) {
                detectDragGestures { change, dragAmount ->
                    change.consumeAllChanges()
                    offsetX += dragAmount.x
                    offsetY += dragAmount.y
                }
            }
    )
}

Un elemento de la IU que se arrastra mediante el dedo

Deslices

El modificador swipeable te permite arrastrar elementos que, cuando se lanzan, se suelen animar en dos o más puntos de anclaje definidos en una orientación. Un uso común es implementar un patrón de "deslizar para descartar".

Es importante tener en cuenta que este modificador no mueve el elemento; solo detecta el gesto. Debes conservar el estado y representarlo en la pantalla, por ejemplo, para mover el elemento mediante el modificador offset.

El estado deslizable es obligatorio en el modificador swipeable y se puede crear y recordar con rememberSwipeableState(). Este estado también proporciona un conjunto de métodos útiles para animar anclajes de manera programática (consulta snapTo, animateTo, performFling y performDrag), así como en las propiedades para observar el progreso de arrastre.

El gesto de deslizar se puede configurar para que tenga diferentes tipos de umbrales, como FixedThreshold(Dp) y FractionalThreshold(Float), y pueden ser diferentes para cada punto de anclaje de combinación "from-to".

Para obtener más flexibilidad, puedes configurar resistance cuando deslices los límites y también el velocityThreshold que animará el deslizamiento al siguiente estado, incluso si no se alcanza el thresholds posicional.

@Composable
fun SwipeableSample() {
    val width = 96.dp
    val squareSize = 48.dp

    val swipeableState = rememberSwipeableState(0)
    val sizePx = with(LocalDensity.current) { squareSize.toPx() }
    val anchors = mapOf(0f to 0, sizePx to 1) // Maps anchor points (in px) to states

    Box(
        modifier = Modifier
            .width(width)
            .swipeable(
                state = swipeableState,
                anchors = anchors,
                thresholds = { _, _ -> FractionalThreshold(0.3f) },
                orientation = Orientation.Horizontal
            )
            .background(Color.LightGray)
    ) {
        Box(
            Modifier
                .offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
                .size(squareSize)
                .background(Color.DarkGray)
        )
    }
}

Un elemento de IU que responde a un gesto de deslizar

Multitoque: desplazamiento lateral, zoom y rotación

Para detectar gestos multitáctiles utilizados para el desplazamiento lateral, el zoom y la rotación, puedes usar el modificador transformable, que no transforma los elementos por sí solo; únicamente detecta los gestos.

@Composable
fun TransformableSample() {
    // set up all transformation states
    var scale by remember { mutableStateOf(1f) }
    var rotation by remember { mutableStateOf(0f) }
    var offset by remember { mutableStateOf(Offset.Zero) }
    val state = rememberTransformableState { zoomChange, offsetChange, rotationChange ->
        scale *= zoomChange
        rotation += rotationChange
        offset += offsetChange
    }
    Box(
        Modifier
            // apply other transformations like rotation and zoom
            // on the pizza slice emoji
            .graphicsLayer(
                scaleX = scale,
                scaleY = scale,
                rotationZ = rotation,
                translationX = offset.x,
                translationY = offset.y
            )
            // add transformable to listen to multitouch transformation events
            // after offset
            .transformable(state = state)
            .background(Color.Blue)
            .fillMaxSize()
    )
}

Un elemento de IU que responde a gestos multitáctiles (desplazamiento lateral, zoom y rotación)

Si necesitas combinar el zoom, el desplazamiento lateral y la rotación con otros gestos, puedes usar el detector PointerInputScope.detectTransformGestures.