flutter-interactividad-gestos

Gestos en Flutter con GestureDetector e InkWell

  • 4 min

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")),
  ),
)
Copied!

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(),
)
Copied!

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"),
  ),
)
Copied!

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"),
  ),
)
Copied!

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)),
    ),
  ),
)
Copied!

Qué widget elegir

La decisión es sencilla basándonos en la experiencia de usuario (UX):

WidgetUso recomendadoEjemplo
InkWellCuando el elemento parece un botón o una tarjeta y quieres feedback visual estándar.Elementos de una lista, botones personalizados, tarjetas.
GestureDetectorCuando 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")),
)
Copied!