flutter-layouts-stack-positioned

Stack y Positioned en Flutter: superponer elementos

  • 4 min

Un Stack es un widget que coloca elementos unos encima de otros, como capas.

Hasta ahora hemos sido muy ordenados. Hemos puesto cosas una al lado de la otra (Row) o una debajo de la otra (Column). Pero el diseño moderno no siempre es una cuadrícula perfecta.

A veces necesitamos poner un texto encima de una imagen. O un círculo rojo de notificación encima de un icono. O un botón flotando en una esquina.

Para trabajar en este “Eje Z” (profundidad), Flutter nos ofrece el Stack.

El concepto de capas

El Stack funciona igual que las capas en Photoshop o una pila de folios en tu mesa.

  1. Pones el primer folio (el fondo).
  2. Pones el segundo folio encima (tapa al anterior).
  3. Y así sucesivamente.

En código, esto significa que el orden de la lista children importa muchísimo.

  • El primer widget de la lista es el que está al fondo (Base).
  • El último widget de la lista es el que está arriba del todo (Cima).
Stack(
  children: [
    // 1. Fondo (Verde)
    Container(width: 300, height: 300, color: Colors.green),
    
    // 2. Encima (Rojo)
    Container(width: 200, height: 200, color: Colors.red),
    
    // 3. Arriba del todo (Azul)
    Container(width: 100, height: 100, color: Colors.blue),
  ],
)
Copied!

En Web usamos z-index para controlar quién tapa a quién. En Flutter no existe z-index. El orden de pintado lo define exclusivamente el orden en la lista.

Alineación general

Por defecto, el Stack apila todo en la esquina superior izquierda (topStart). Si queremos que todos los elementos se centren o se vayan a una esquina, usamos la propiedad alignment.

Stack(
  alignment: Alignment.center, // Todos los hijos se centran
  children: [
    Container(width: 300, height: 300, color: Colors.green),
    Text("Hola Mundo"), // Aparecerá centrado sobre el verde
  ],
)
Copied!

Positioned: control de la posición

La alineación está bien para cosas sencillas, pero ¿y si quiero el texto abajo a la derecha y el icono arriba a la izquierda?

Para eso, debemos envolver al hijo dentro de un widget Positioned.

Este widget solo funciona si es descendiente directo de un Stack. Nos permite fijar la posición usando distancias: top, bottom, left, right.

Stack(
  children: [
    // Fondo: Una imagen
    Container(color: Colors.blue, width: 300, height: 300),
    
    // Elemento 1: Texto abajo (a 10px del borde inferior)
    Positioned(
      bottom: 10,
      left: 10,
      child: Text("Título de la imagen", style: TextStyle(color: Colors.white)),
    ),
    
    // Elemento 2: Icono arriba a la derecha
    Positioned(
      top: 10,
      right: 10,
      child: Icon(Icons.favorite, color: Colors.white),
    ),
  ],
)
Copied!

Si usas Positioned, el widget hijo puede cambiar de tamaño. Si fijas left: 0 y right: 0, estás forzando al hijo a estirarse todo el ancho del Stack.

Ejemplo práctico: indicador de notificación

Un caso de uso típico es el icono que indica que tienes mensajes sin leer. Vamos a construirlo:

  1. Base: El icono de la campana.
  2. Capa superior: Un circulito rojo posicionado en la esquina.
Stack(
  clipBehavior: Clip.none, // Permite que el punto rojo sobresalga un poco si queremos
  children: [
    // 1. El Icono base
    Icon(Icons.notifications, size: 50, color: Colors.grey),

    // 2. El punto rojo
    Positioned(
      right: 0,
      top: 0,
      child: Container(
        padding: EdgeInsets.all(4),
        decoration: BoxDecoration(
          color: Colors.red,
          shape: BoxShape.circle,
        ),
        child: Text(
          '3',
          style: TextStyle(color: Colors.white, fontSize: 12),
        ),
      ),
    )
  ],
)
Copied!

¿Cuánto mide un Stack?

El Stack intenta ajustarse al tamaño de sus hijos no posicionados.

  • Si tiene una imagen de fondo de 300x300 (no posicionada), el Stack medirá 300x300.
  • Si todos los hijos están posicionados, el Stack intenta ocupar todo el tamaño que permiten las restricciones de su padre. Si recibe restricciones no acotadas, necesitarás proporcionarle un tamaño.

Regla práctica: Un hijo de fondo no posicionado suele ser la forma más sencilla de definir el tamaño del lienzo.

Positioned.fill

Un atajo muy útil. Si quieres que un elemento ocupe todo el espacio del Stack (por ejemplo, una imagen de fondo o un velo semitransparente para oscurecer), usa Positioned.fill.

Stack(
  children: [
    Positioned.fill(
      child: Image.network("...", fit: BoxFit.cover),
    ),
    Center(child: Text("Texto sobre imagen")),
  ],
)
Copied!

Es equivalente a poner top: 0, bottom: 0, left: 0, right: 0.