Un layout flexible es una forma de repartir el espacio disponible entre varios widgets.
Ya sabemos crear cajas bonitas con Container. Pero una App no es una caja flotando en la nada. Son muchas cajas: unas encima de otras, otras al lado…
Para organizar los elementos en la pantalla, Flutter utiliza un sistema muy parecido al Flexbox de CSS. Los reyes indiscutibles de este sistema son Row (Fila) y Column (Columna).
Dominar estos dos widgets y sus ejes de alineación es el 80% del éxito al maquetar una pantalla.
Row y Column
La diferencia es obvia:
- Row: Coloca los widgets horizontalmente (uno al lado del otro).
- Column: Coloca los widgets verticalmente (uno debajo del otro).
A diferencia del Container que solo acepta un hijo (child), estos layouts aceptan una lista de hijos (children).
Column(
children: [
Text("Elemento 1"),
Text("Elemento 2"),
BotonBonito(),
],
)
Eje principal y eje cruzado
Aquí es donde todo el mundo se confunde al principio. Para alinear objetos, no usamos “Izquierda/Derecha” o “Arriba/Abajo”. Usamos Eje Principal (MainAxis) y Eje Cruzado (CrossAxis).
La orientación de estos ejes cambia dependiendo de si estás en una Fila o una Columna.
- MainAxis: Es el eje Vertical (↕). Define cómo se distribuyen los hijos de arriba a abajo.
- CrossAxis: Es el eje Horizontal (↔). Define cómo se alinean los hijos a izquierda/derecha.
- MainAxis: Es el eje Horizontal (↔). Define cómo se distribuyen los hijos de izquierda a derecha.
- CrossAxis: Es el eje Vertical (↕). Define cómo se alinean los hijos arriba/abajo.
Alineando elementos
Para controlar esto, usamos las propiedades mainAxisAlignment y crossAxisAlignment.
Define cómo se reparten los hijos en el espacio disponible del eje principal.
start: Al principio (Arriba en Col, Izq en Row).center: Al centro.end: Al final.spaceBetween: Pone todo el espacio libre entre los elementos.spaceAround: Pone espacio alrededor.
Column(
mainAxisAlignment: MainAxisAlignment.center, // Centrados verticalmente
children: [ ... ],
)
Define cómo se colocan en el eje contrario.
start: Pegados al inicio (Izquierda en Col, Arriba en Row).center: Centrados.stretch: Estirarse para ocupar todo el ancho (o alto) posible.
Column(
crossAxisAlignment: CrossAxisAlignment.stretch, // Ocupan todo el ancho
children: [ ... ],
)
Una Column por defecto intenta ocupar todo el alto vertical posible (MainAxisSize.max), pero solo el ancho que necesiten sus hijos.
El problema del desbordamiento
Seguro que te ha pasado. Pones muchos textos en una Row y de repente… ¡Zas! Aparece una franja amarilla y negra de precaución y un error en consola: “RenderFlex overflowed by X pixels”.
Esto ocurre porque los hijos suman más ancho del que la pantalla tiene. Row y Column son layouts “inflexibles” por defecto; no saben encoger a sus hijos automáticamente.
Una solución habitual es Expanded, aunque según el diseño también puede convenir usar Flexible, permitir saltos de línea, envolver el contenido o hacerlo desplazable.
Expanded y Flexible
Expanded es un widget que le dice a un hijo de una Row/Column:
“Oye, deja de tener un tamaño fijo. Expándete y ocupa todo el espacio libre que sobre”.
Row(
children: [
Icon(Icons.person), // Tamaño fijo (el del icono)
// El texto ocupará TODO el espacio restante, empujando lo demás
Expanded(
child: Text("Este es un texto muy largo que ahora sí cabe porque salta de línea..."),
),
Icon(Icons.arrow_forward), // Tamaño fijo
],
)
Proporciones con flex
Si tienes varios Expanded, se repartirán el espacio a partes iguales. Pero puedes usar la propiedad flex para dar prioridades.
Row(
children: [
Expanded(flex: 2, child: Container(color: Colors.red)), // Ocupa 2/3
Expanded(flex: 1, child: Container(color: Colors.blue)), // Ocupa 1/3
],
)
SizedBox como separador
Cuando solo necesitas separar dos elementos, un SizedBox expresa con claridad el tamaño del hueco.
Column(
children: [
Text("Titulo"),
SizedBox(height: 20), // Un hueco vertical de 20px
Text("Subtítulo"),
],
)
Es mucho más legible leer SizedBox(height: 20) que leer un Container(margin: ...) anidado.