Si te estás metiendo en el mundo de Jetpack Compose, te habrás dado cuenta de que hacer que una aplicación se sienta fluida y natural no es moco de pavo. No basta con poner un botón que cambie de color; la verdadera magia ocurre cuando el usuario puede deslizar, pellizcar o arrastrar elementos con una precisión quirúrgica. Dominar el manejo gestual es lo que diferencia a una app mediocre de una que parece sacada de la tienda de Apple o Google.
Para lograr esto, Compose nos ofrece un abanico de herramientas que van desde lo más sencillo hasta lo más técnico. No hace falta reinventar la rueda en cada pantalla, pero sí saber exactamente cuándo usar un modificador estándar y cuándo meterse en el barro de los eventos de puntero. En este artículo vamos a desglosar cada nivel de control para que tus interfaces respondan exactamente como tú quieres.
Conceptos fundamentales: Punteros, eventos y gestos
Antes de tirar código, hay que tener claros los términos. Un puntero es básicamente cualquier cosa física que usamos para tocar la pantalla, ya sea el dedo, un lápiz óptico o incluso un ratón en tablets. Compose clasifica esto mediante PointerType para saber con qué estamos interactuando.
Por otro lado, un evento de puntero es la unidad mínima de interacción; es ese instante preciso en el que pones el dedo sobre el cristal. Toda la chicha de esta interacción se guarda en la PointerEvent. Ahora bien, un gesto es algo más complejo: es una serie de eventos organizados que el sistema interpreta como una acción concreta, como un toque rápido o un arrastre prolongado.
Niveles de abstracción en el manejo gestual
Compose es muy inteligente y nos da tres capas para gestionar los gestos, y la regla de oro es: usa siempre la capa más alta que resuelva tu problema.
- Componentes integrados: Es el nivel más fácil. Elementos como
ButtonoLazyColumnya traen la lógica de clic o desplazamiento inyectada de serie. - Modificadores de gestos: Si necesitas que un
Boxsea interactivo, usas modificadores comoclickableoverticalScroll. Estos no solo detectan el toque, sino que añaden semántica de accesibilidad y efectos visuales. - Modificador pointerInput: Aquí es donde ocurre la magia pesada. Si necesitas algo muy loco, como un arrastre que solo se active tras mantener presionado con tres dedos, tienes que usar este modificador para acceder a los eventos en bruto.
Cómo crear gestos personalizados y complejos
Cuando entramos en el terreno de
pointerInput, nos encontramos con el awaitPointerEventScope. Este entorno nos permite suspender la corrutina hasta que ocurra un evento. Si quieres detectar toques y arrastres en el mismo elemento, ten cuidado: funciones como detectTapGestures bloquean la corrutina, impidiendo que otros detectores se ejecuten. La solución es simple: añade varios modificadores pointerInput independientes en lugar de uno solo con varias funciones.Para gestionar el ciclo de vida de un gesto, lo ideal es usar awaitEachGesture. Este método reinicia la escucha cada vez que todos los dedos se levantan de la pantalla, marcando el final de la acción. Además, para interacciones multitáctiles como el zoom, Compose ofrece herramientas como
calculateZoom y calculatePan, que nos ahorran tener que hacer cálculos matemáticos complejos con las coordenadas de cada dedo.El flujo y consumo de eventos
Un problema típico es cuando tienes un botón dentro de una lista y ambos quieren reaccionar al toque. Para evitar peleas, Compose usa un sistema de tres pases de propagación>:
- Pase Inicial: El evento baja desde la raíz hasta el hijo. Permite que un padre intercepte la acción antes que el hijo.
- Pase Principal: El evento sube desde el hijo hacia la raíz. Es donde ocurre la mayoría de la acción y donde se consumen los gestos.
- Pase Final: El evento vuelve a bajar. Sirve para que el padre reaccione a que el hijo ya ha consumido el evento, útil por ejemplo para quitar el efecto de rizo de un botón si la lista empezó a hacer scroll.
Es vital que, al crear gestos propios, llames a PointerInputChange.consume(). Si no lo haces, el evento seguirá propagándose y podrías terminar activando dos acciones distintas al mismo tiempo, lo cual es un desastre para la experiencia de usuario.
Estrategias de arrastre y deslizamiento (Swipes)
Para mover cosas en la pantalla, tenemos dos caminos. El modificador
draggable es genial para una sola dirección y te da la distancia en píxeles, pero recuerda que no mueve el elemento por sí solo; tú debes actualizar el estado y usar Modifier.offset para reflejar el movimiento.Si buscas algo más avanzado como el clásico «deslizar para borrar», la opción es
anchoredDraggable (que sustituyó al antiguo swipeable). Este sistema permite definir puntos de anclaje y umbrales de sensibilidad, haciendo que el elemento «salte» a una posición específica una vez que el usuario ha deslizado lo suficiente.Diseño adaptable y ergonomía táctil
No podemos hablar de gestos sin hablar de dónde ponemos los dedos. En un móvil, el pulgar llega fácilmente a la parte inferior, por lo que un
NavigationBar es lo ideal. Pero en una tablet, el usuario suele sujetar el dispositivo por los lados, haciendo que un Navigation Rail sea mucho más cómodo.Utilizando la librería de Material 3 Adaptive, podemos usar el
NavigationSuiteScaffold para que la navegación cambie automáticamente según el tamaño de la ventana. Para aprovechar el espacio en pantallas grandes, el patrón ListDetailPaneScaffold es la joya de la corona, permitiendo mostrar una lista y el detalle del elemento lado a lado, navegando entre paneles de forma fluida con un BackHandler bien configurado.Continúar leyendo...