Un gesto es una interacción del usuario con la pantalla.
En Flutter, la mayoría de widgets visuales (Container, Image, Text, Row) no tienen una propiedad onPressed. Son elementos pasivos de diseño.
Para dotarlos de vida, necesitamos “envolverlos” en widgets especiales capaces de escuchar al usuario. Tenemos dos grandes protagonistas: el invisible GestureDetector y el visual InkWell.
GestureDetector: detección sin efecto visual
El GestureDetector es un widget que no pinta nada en pantalla (es invisible), pero es capaz de detectar casi cualquier movimiento del dedo sobre su hijo.
Es la navaja suiza de la interactividad.
Detectar un toque con onTap
Supongamos que tenemos un Container azul y queremos que haga algo al tocarlo.
GestureDetector(
onTap: () {
print("¡Has tocado la caja!");
},
child: Container(
width: 100,
height: 100,
color: Colors.blue,
child: Center(child: Text("Tócame")),
),
)
Gestos avanzados
Lo potente del GestureDetector es que va mucho más allá del simple clic:
onDoubleTap: Doble toque rápido (como el “me gusta” de Instagram).onLongPress: Mantener pulsado (para menús contextuales).onPanUpdate: Arrastrar el dedo (para mover objetos).onScaleUpdate: Pellizcar (para hacer zoom).
GestureDetector(
onTap: () => print("Toque simple"),
onDoubleTap: () => print("Doble toque - Like!"),
onLongPress: () => print("Abriendo menú..."),
child: MiWidget(),
)
GestureDetector es puramente lógico. Detecta el gesto, ejecuta tu código, pero no da ninguna respuesta visual al usuario. El botón no se ilumina ni cambia de color. Para el usuario puede parecer que la App se ha bloqueado si la acción tarda en ejecutarse.
InkWell: respuesta visual Material
Si usas Material Design, conviene proporcionar respuesta visual. El usuario necesita saber que ha tocado el elemento.
El InkWell es un widget que, al tocarlo, genera una “onda de tinta” (Ripple Effect) que se expande desde el dedo.
InkWell(
onTap: () {
print("¡Efecto de onda!");
},
child: Container(
padding: EdgeInsets.all(20),
child: Text("Botón plano con efecto"),
),
)
El problema del fondo opaco
Este es el error número 1 al usar InkWell.
Si pones un InkWell y dentro un Container con color… ¡la onda no se ve!
// ❌ MAL: El color del Container TAPA la onda del InkWell
InkWell(
onTap: () {},
child: Container(
color: Colors.blue, // Esto es opaco y está ENCIMA de la tinta
child: Text("No verás la onda"),
),
)
El InkWell pinta la tinta en el widget Material más cercano en el fondo. Si tu Container hijo tiene color, actúa como una capa de pintura opaca encima de la tinta.
La solución: widget Ink
Para solucionar esto, Flutter nos ofrece el widget Ink. Es como un Container, pero diseñado específicamente para trabajar con InkWell y pintar la tinta por encima de la imagen o color de fondo.
// ✅ BIEN: Usamos el widget Ink para el diseño
Ink(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(10),
),
child: InkWell(
borderRadius: BorderRadius.circular(10), // Para que la onda respete los bordes
onTap: () {},
child: Container(
padding: EdgeInsets.all(20),
child: Text("Ahora sí se ve la onda", style: TextStyle(color: Colors.white)),
),
),
)
Qué widget elegir
La decisión es sencilla basándonos en la experiencia de usuario (UX):
| Widget | Uso recomendado | Ejemplo |
|---|---|---|
| InkWell | Cuando el elemento parece un botón o una tarjeta y quieres feedback visual estándar. | Elementos de una lista, botones personalizados, tarjetas. |
| GestureDetector | Cuando necesitas gestos complejos o cuando la interacción debe ser sutil/invisible. | Hacer zoom en una foto, deslizar para borrar, pausar un vídeo al tocar. |
Deslizar elementos con Dismissible
Un gesto muy común en móviles es “deslizar para borrar” (Swipe to dismiss). Aunque podríamos hacerlo con GestureDetector, Flutter nos regala el widget Dismissible.
Dismissible(
key: Key('item_id'), // Necesario para identificar qué borramos
background: Container(color: Colors.red), // Lo que se ve al deslizar
onDismissed: (direction) {
// Aquí borramos el elemento de nuestra lista de datos
setState(() {
misDatos.removeAt(index);
});
},
child: ListTile(title: Text("Deslízame para borrar")),
)