Construyendo Layouts en Flutter

Lo que aprenderás:

  • Como trabaja el mecanismo de layout de Flutter.
  • Como disponer widgets vertical y horizontalmente.
  • Como construir un layout en Flutter.

Esta es una guía para construir layouts en Flutter. Construirás el layout para la siguiente captura de pantalla:

aplicación lagos terminada que construirás en 'Construyendo un Layout'

Esta guía da un paso atrás para explicar el enfoque de Flutter para los layouts y muestra como colocar un solo widget en la pantalla. Después de una discusión sobre como disponer widgets horizontal y verticalmente, algunos de los widgets de layout más comunes son cubiertos.

Construyendo un layout

Si buscas tener una “visión general” para entender el mecanismo de los layouts, empieza con Enfoque de los layouts en Flutter.

Paso 0: Preparar

Primero, obtén el código:

Después, añade la imagen al ejemplo:

  • Crea un directorio images en el raíz del proyecto.
  • Añade lake.jpg. (Nota: wget no funcionará para guardar este archivo binario.)
  • Actualiza el fichero pubspec.yaml para incluir una etiqueta assets. Esto hace que la imagen este disponible para tu código.

Paso 1: Esquema del layout

El primer paso es dividir el layout en sus elementos básicos:

  • Identifica las filas y las columnas.
  • ¿Debe el layout incluir un grid?
  • ¿Tiene elementos en capas superpuestas?
  • ¿Necesita el UI tabs?
  • Observa que áreas requieren alineación, padding o bordes.

Primero, identifica los elementos más grandes. En este ejemplo, cuatro elementos están organizados en una columna: una imagen, dos filas, y un bloque de texto.

Esquematizando las filas en la captura de pantalla de 'lakes'

Después, esquematiza cada fila. La primera fila, llamada ‘Title section’, tiene 3 hijos: una columna de texto, un icono estrella, y un número. Su primer hijo, la columna, contiene 2 líneas de texto. Esta primera columna toma mucho espacio, por lo tanto debe ser envuelto en un widget Expanded.

esquematiza los widgets en la sección Título

La segunda fila, llamada la ‘Button section’, también tiene 3 hijos: cada hijo es una columna que contiene un icono y un texto.

esquematiza los widgets en la sección button

Cuando el layout ha sido esquematizado, es más fácil tomar un enfoque ascendente para implementarlo. Para reducir la confusión visual de código de layout profundamente anidado, ubica alguna de su implementación en variables y funciones.

Paso 2: Implementar la fila ‘title’

Primero, construirás la columna izquierda en la sección ‘title’. Poniendo un Column dentro de un widget Expanded extiendes la columna para usar todo el espacio libre restante en la fila. Ajustando la propiedad crossAxisAlignment a CrossAxisAlignment.start posicionamos la columna al principio de la fila.

Poniendo la primera fila de texto dentro de un Container posibilitamos la adicción de padding. El segundo hijo en Column, también texto, se dibuja gris.

Los dos últimos elementos en la fila ‘title’ son un icono estrella, dibujado rojo, y un texto “41”. Pon la fila entera en un Container y dale un padding de 32 pixels a lo largo de cada borde.

Aquí está el código que implementa la fila ‘title’.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Widget titleSection = Container(
      padding: const EdgeInsets.all(32.0),
      child: Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(
                  padding: const EdgeInsets.only(bottom: 8.0),
                  child: Text(
                    'Oeschinen Lake Campground',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                Text(
                  'Kandersteg, Switzerland',
                  style: TextStyle(
                    color: Colors.grey[500],
                  ),
                ),
              ],
            ),
          ),
          Icon(
            Icons.star,
            color: Colors.red[500],
          ),
          Text('41'),
        ],
      ),
    );
  //...
}

Paso 3: Implementa la fila ‘button’

La sección ‘button’ contiene 3 columnas que usan el mismo layout—un icono sobre una fila de texto. Las columnas en esta fila están espaciadas uniformemente, y el texto y los iconos están dibujados con el color primario, que está establecido azul en el método build() de la app:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //...

    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),

    //...
}

Dado que el código para construir cada fila será casi idéntico, es más eficiente crear una función anidada, como buildButtonColumn(), que toma un Icon y un Text, y devuelve una columna con estos widgets dibujados en el color primario.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //...

    Column buildButtonColumn(IconData icon, String label) {
      Color color = Theme.of(context).primaryColor;

      return Column(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(icon, color: color),
          Container(
            margin: const EdgeInsets.only(top: 8.0),
            child: Text(
              label,
              style: TextStyle(
                fontSize: 12.0,
                fontWeight: FontWeight.w400,
                color: color,
              ),
            ),
          ),
        ],
      );
    }
  //...
}

La función build añade el icono directamente a la columna. Pone el texto en un Container para añadir padding alrededor del texto, separándolo del icono.

Construye la fila que contiene estas columnas llamando a la función y pasando el icon y el texto específico de cada columna. Alinea las columnas a lo largo de su eje principal usando MainAxisAlignment.spaceEvenly para organizar el espacio libre equitativamente antes, entre y después de cada columna.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //...

    Widget buttonSection = Container(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          buildButtonColumn(Icons.call, 'CALL'),
          buildButtonColumn(Icons.near_me, 'ROUTE'),
          buildButtonColumn(Icons.share, 'SHARE'),
        ],
      ),
    );
  //...
}

Paso 4: Implementar la sección de texto

Define la sección de texto, que es bastante larga, como una variable. Pon el texto en un Container para habilitar el poder añadir 32 pixels de padding a lo largo de cada borde. La propiedad softwrap indica como deberá romperse el texto en saltos de línea suaves, como puntos o comas.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //...
    Widget textSection = Container(
      padding: const EdgeInsets.all(32.0),
      child: Text(
        '''
Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese Alps. Situated 1,578 meters above sea level, it is one of the larger Alpine Lakes. A gondola ride from Kandersteg, followed by a half-hour walk through pastures and pine forest, leads you to the lake, which warms to 20 degrees Celsius in the summer. Activities enjoyed here include rowing, and riding the summer toboggan run.
        ''',
        softWrap: true,
      ),
    );
  //...
}

Paso 5: Implementar la sección de la imagen

Tres de las cuatro columnas están completas, solo queda la imagen. Esta imagen está disponible online bajo licencia Creative Commons, pero es grande y lenta de obtener. En el Paso 0 incluiste la imagen en el proyecto actualizando el fichero pubspec, ahora puedes referenciarla desde tu código:

return MaterialApp(
//...
body: ListView(
  children: [
    Image.asset(
      'images/lake.jpg',
      height: 240.0,
      fit: BoxFit.cover,
    ),
    // ...
  ],
),
//...
);

BoxFit.cover dice al framework que la imagen debe ser tan pequeña como sea posible pero cubriendo enteramente su render box.

Paso 6: Poner todo junto

En el último paso, ensamblarás todas las piezas juntas. Los widgets están organizados en un ListView, mejor que en un Column, porque el ListView posibilita el scroll cuando se ejecuta la aplicación en dispositivos pequeños.

//...
return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Top Lakes'),
        ),
        body: ListView(
          children: [
            Image.asset(
              'images/lake.jpg',
              width: 600.0,
              height: 240.0,
              fit: BoxFit.cover,
            ),
            titleSection,
            buttonSection,
            textSection,
          ],
        ),
      ),
    );
//...

Código Dart: main.dart
Imagen: imágenes
Pubspec: pubspec.yaml

!Esto es¡ Cuando la app hace hot reload, deberías ver el mismo layout mostrado en las capturas de pantalla. Puedes añadir interactividad a este layout siguiendo Agregando interactividad a Tu Aplicación Flutter.


Enfoque de los layouts en Flutter

¿Qué aprenderás?

  • Los widgets son clases usadas para construir UIs.
  • Los widgets son usados por ambos, layout y elementos de la UI.
  • Componer widgets simples para construir widgets complejos.

El núcleo del mecanismo de layout de Flutter son los widgets. En Flutter, casi todo es un widget—incluso los modelos de layout son widgets. Las imágenes, iconos, y texto que ves en una aplicación Flutter son todos widgets. Pero cosas que no ves son también widgets, como son los rows, columns, y grids que organizan, restringen, y alinean los widgets visibles.

Creas un layout componiendo widgets para construir widgets más complejos. Por ejemplo, la captura de pantalla a la izquierda muestra 3 iconos con una etiqueta bajo cada uno:

layout de ejemplo      layout de ejemplo con depuración visual encendida

La segunda captura de pantalla muestra el layout visualmente, mostrando una fila de 3 columnas donde cada columna contiene un icono y una etiqueta.

Aquí está un diagrama del árbol de widgets para este UI:

árbol de nodos representando el ejemplo de layout

La mayoría de esto debería verse como es de esperar, pero es posible que te preguntes sobre los Containers (mostrados en rosa). Container es un widget que te permite personalizar su widget hijo. Usa un Container cuando quieras añadir padding, márgenes, bordes, o un color de background, por nombrar alguna de sus capacidades.

En este ejemplo, cada widget Text es colocado en un Container para añadir márgenes. El Row entero está también colocado en un Container para añadir padding alrededor de la fila.

El resto del UI en este ejemplo es controlado por sus propiedades. Establece un color para el Icon usando su propiedad color. Usa la propiedad style de Text para fijar la fuente, su color, grosor, etc. Columns y Rows tienen propiedades que permiten especificar como sus hijos son alineados vertical u horizontalmente, y cuanto espacio deberían ocupar.


Layout de un widget

¿Qué aprenderás?

  • Incluso la aplicación en sí es un widget.
  • Es fácil crear un widget y añadirlo a un widget de layout.
  • Para mostrar el widget en el dispositivo, añade el widget de layout al widget app.
  • Es más fácil usar Scaffold, un widget de la biblioteca Material Components, que provée un banner por defecto, un color de background, y tiene una API para añadir drawers, snack bars, y bottom sheets.
  • Si lo prefieres, puedes construir una aplicación que solo use widgets standard de la biblioteca de widgets.

Como hacer el layout de un widget simple en Flutter? Esta sección enseña como crear un widget simple y mostrarlo en la pantalla. También muestra el código completo para una sencilla aplicación Hello World.

En Flutter, toma solo unos pocos paso poner texto, un icono, o una imagen en la pantalla.

  1. Elige un widget de layout para sostener el objeto.
    Elige entre una variedad de widgets de layout basándote en como quieres alinear o restringir el widget visible, ya que estas características generalmente se transmiten al widget contenido. Este ejemplo usa Center el cual centra su contenido horizontal y verticalmente.

  2. Crea un widget para sostener el objeto visible.

    Por ejemplo, crea un widget Text:

    Text('Hello World', style: TextStyle(fontSize: 32.0))

    Crea un widget Image:

    Image.asset('images/myPic.jpg', fit: BoxFit.cover)

    Crea un widget Icon:

    Icon(Icons.star, color: Colors.red[500])
  3. Añade el widget visible al widget de layout.
    Todos los widgets de layout tienen una propiedad child si este toma un único hijo (por ejemplo, Center o Container), o una propiedad children si este toma una lista de widgets (por ejemplo, Row, Column, ListView, o Stack).

    Añade el widget Text al widget Center:

    Center(
      child: Text('Hello World', style: TextStyle(fontSize: 32.0))
  4. Añade el widget de layout a la página.
    Una aplicación Flutter es, en sí misma, un widget y la mayoría de los widgets tienen un método build(). Declarando el widget en el método build de la app se muestra el widget en el dispositivo.

    Para una aplicación Material, puedes añadir el widget Center directamente a la propiedad body de la página principal.

    class _MyHomePageState extends State<MyHomePage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Text('Hello World', style: TextStyle(fontSize: 32.0)),
          ),
        );
      }
    }

    Para una app no basada en Material, puedes añadir el widget Center al método build() de la app:

    // Esta app no usa Material Components, como es Scaffold.
    // Normalmente, una app que no usa Scaffold tiene un fondo negro
    // y el color por defecto del texto es negro. Esta app cambia su fondo
    // a blanco y el color de su texto a gris oscuro para imitar una app Material.
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          decoration: BoxDecoration(color: Colors.white),
          child: Center(
            child: Text('Hello World',
                textDirection: TextDirection.ltr,
                style: TextStyle(fontSize: 40.0, color: Colors.black87)),
          ),
        );
      }
    }

    Note que, por defecto, las aplicaciones que no usan Material no tienen un AppBar, título, o color de fondo. Si tú quieres estas características en una aplicación que no usa Material, tienes que construirlas tú mismo. Esta app cambia el color de fondo a blanco y el texto a gris oscuro para imitar una app Material.

!Esto es todo¡ Cuando ejecutas la app, deberías ver:

captura de pantalla de un fondo blanco con 'Hello World' en gris.

Código Dart (Material app): main.dart
Código Dart (widgets-only app): main.dart


Layout de múltiples widgets horizontal y verticalmente

Uno de los patrones de layout más comunes es organizar los widgets vertical u horizontalmente. Puedes usar un widget Row para organizar widgets horizontalmente, and a Column widget to arrange widgets vertically.

¿Qué aprenderás?

  • Row y Column son dos de los patrones de layout más comúnmente usados.
  • Ambos, Row y Column, toman una lista de widgets hijos.
  • Un widget hijo puede ser así mismo un Row, Column, u otro widget complejo.
  • Puedes especificar como un Row o un Column alinea sus hijos, vertical y horizontalmente.
  • Puedes especificar ajustes o restricciones a los widgets hijos.
  • Puedes especificar como los widgets hijos, del Row o Column, usan el espacio disponible.

Contenidos

Para crear una fila o una columna en Flutter, añades una lista de widgets hijos a un widget Row o Column. A su vez, cada hijo puede ser en sí mismo una fila o una columna, y así sucesivamente. El ejemplo siguiente muestra como es posible anidar filas o columnas dentro de otras filas o columnas.

Este layout esta orginzado como un Row. La fila contiene dos hijos: una columna en la izquierda, y una imagen en la derecha:

captura de pantalla con llamadas mostrando la fila conteniendo dos hijos: una columna y una imagen.


El árbol de widgets de la columna izquierda anida filas y columnas.

diagrama mostrando una columna izquierda dividida en sus sub-filas y sub-columnas


Implementarás algo del código del layout de Pavlova en Anidando filas y columnas.

Alineando widgets

Tu controlas como una fila o una columna alinea sus hijos usando las propiedades mainAxisAlignment y crossAxisAlignment. Para una fila, el eje principal, Main Axis, discurre horizontalmente y el eje transversal, Cross Axis, discurre verticalmente. Para una columna, el eje principal, Main Axis, discurre verticalmente y el eje transversal, Cross Axis, discurre horizontalmente.

diagrama mostrando el eje principal y transversal de una fila

diagrama mostrando el eje principal y transversal de una columna

Las clases MainAxisAlignment y CrossAxisAlignment ofrecen una variedad de constantes para controlar el alineamiento.

En el siguiente ejemplo, cada una de las 3 imágenes tiene 100 píxeles de ancho. El render box (en este caso, la pantalla entera) tiene más de 300 píxeles de ancho, entonces fijando la alineación en el main axis a spaceEvenly divides el espacio horizontal libre equitativamente entre, después y antes de cada imagen.

appBar: AppBar(
  title: Text(widget.title),
),
body: Center(
  child: Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: [
      Image.asset('images/pic1.jpg'),
una fila mostrando 3 imágenes repartidas equitativamente en la fila

Código Dart: main.dart
Imágenes: imágenes
Pubspec: pubspec.yaml

Las columnas trabajan de la misma manera que las filas. El siguiente ejemplo muestra una columna de 3 imágenes, cada una tiene 100 píxeles de alto. La altura del render box (en este caso, la pantalla entera) tiene más de 300 píxeles, entonces fijando el main axis a spaceEvenly divides el espacio vertical libre equitativamente entre, por encima, y por debajo de cada imagen.

appBar: AppBar(
  title: Text(widget.title),
),
body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: [
      Image.asset('images/pic1.jpg'),

Código Dart: main.dart
Imágenes: imágenes
Pubspec: pubspec.yaml

una columna mostrando 3 imágenes espaciadas equitativamente en la columna

Dimensionando widgets

Quizás quieras que un widget ocupe el doble de espacio que sus hermanos. Puedes situar el hijo de una fila o una columna en un widget Expanded para controlar el dimensionado del widget a lo largo del main axis. El widget Expanded widget tiene una propiedad flex, un entero que determina el factor flex para un widget. El valor por defecto del factor flex para un widget Expanded es 1.

Por ejemplo, para crear una fila de tres widgets en la cual el widget central es el doble de ancho que los otros dos widgets, establece el factor flex del widget central a 2:

appBar: AppBar(
  title: Text(widget.title),
),
body: Center(
  child: Row(
    crossAxisAlignment: CrossAxisAlignment.center,
    children: [
      Expanded(
        child: Image.asset('images/pic1.jpg'),
      ),
      Expanded(
        flex: 2,
        child: Image.asset('images/pic2.jpg'),
      ),
      Expanded(

una fila de 3 imágenes con la imagen del cenro el doble de ancho que las otras

Código Dart: main.dart
Imágenes: imágenes
Pubspec: pubspec.yaml

Para corregir en el ejemplo anterior donde la fila de 3 imágenes fue demasiado ancho para su render box, y resulto en una tira roja, envuelve cada widget en un widget Expanded. Por defecto, cada widget tiene un factor flex de 1, asignando un tercio de la fila a cada widget.

appBar: AppBar(
  title: Text(widget.title),
),
body: Center(
  child: Row(
    crossAxisAlignment: CrossAxisAlignment.center,
    children: [
      Expanded(
        child: Image.asset('images/pic1.jpg'),
      ),
      Expanded(
        child: Image.asset('images/pic2.jpg'),
      ),
      Expanded(

una fila de 3 imágenes que son demasiado anchas, pero cada una está restringida para coger solo 1/3 del espacio disponible de su fila

Código Dart: main.dart
Imágenes: imágenes
Pubspec: pubspec.yaml

Empaquetando widgets

Por defecto, una fila o columna ocupa tanto espacio a lo largo de su main axis como sea posible, pero si quieres empaquetar a los hijos más juntos, establece su mainAxisSize a MainAxisSize.min. El siguiente ejemplo usa esta propiedad para empaquetas los iconos estrella juntos.

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    var packedRow = Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Icon(Icons.star, color: Colors.green[500]),
        Icon(Icons.star, color: Colors.green[500]),
        Icon(Icons.star, color: Colors.green[500]),
        Icon(Icons.star, color: Colors.black),
        Icon(Icons.star, color: Colors.black),
      ],
    );

  // ...
}

una fila de 5 estrellas, empaquetadas juntas en el medio de una fila

Código Dart: main.dart
Iconos: Icons class
Pubspec: pubspec.yaml

Anidando filas y columnas

El framework de layouts te permite anidar filas y columnas dentro de otras filas y columnas tan profundamente como necesites. Echa un vistazo al código para la sección enmarcada del siguiente layout:

una captura de pantallas de la app pavlova, con las filas de puntuaciones e iconos remarcados en rojo

La sección remarcada está implementada como dos filas. La fila de puntuaciones contiene cinco estrellas y el número de opiniones. La fila de iconos contiene tres columnas de iconos y texto.

El árbol de widgets para la fila de puntuaciones:

un árbol de nodos mostrando los widgets en la fila de puntuaciones


La variable ratings crea una fila conteniendo un fila más pequeña de 5 iconos estrella, y texto:

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    //...

    var ratings = Container(
      padding: EdgeInsets.all(20.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Icon(Icons.star, color: Colors.black),
              Icon(Icons.star, color: Colors.black),
              Icon(Icons.star, color: Colors.black),
              Icon(Icons.star, color: Colors.black),
              Icon(Icons.star, color: Colors.black),
            ],
          ),
          Text(
            '170 Reviews',
            style: TextStyle(
              color: Colors.black,
              fontWeight: FontWeight.w800,
              fontFamily: 'Roboto',
              letterSpacing: 0.5,
              fontSize: 20.0,
            ),
          ),
        ],
      ),
    );
    //...
  }
}

La fila de iconos, bajo la fila de puntuaciones, contiene 3 columnas; cada columna contiene un icono y dos líneas de texto, como puedes ver en el árbol de widgets:

un árbol de nodes para los widgets en la fila de iconos

La variable iconList define la fila de iconos:

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    // ...

    var descTextStyle = TextStyle(
      color: Colors.black,
      fontWeight: FontWeight.w800,
      fontFamily: 'Roboto',
      letterSpacing: 0.5,
      fontSize: 18.0,
      height: 2.0,
    );

    // DefaultTextStyle.merge te permite crear un estilo de texto
    // por defecto que es heredado por este hijo y sus subsiguientes hijos.
    var iconList = DefaultTextStyle.merge(
      style: descTextStyle,
      child: Container(
        padding: EdgeInsets.all(20.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Column(
              children: [
                Icon(Icons.kitchen, color: Colors.green[500]),
                Text('PREP:'),
                Text('25 min'),
              ],
            ),
            Column(
              children: [
                Icon(Icons.timer, color: Colors.green[500]),
                Text('COOK:'),
                Text('1 hr'),
              ],
            ),
            Column(
              children: [
                Icon(Icons.restaurant, color: Colors.green[500]),
                Text('FEEDS:'),
                Text('4-6'),
              ],
            ),
          ],
        ),
      ),
    );
    // ...
  }
}

La variable leftColumn contiene las filas de puntuaciones e iconos, y también tiene el título y el texto que describe la Pavlova:

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    //...

    var leftColumn = Container(
      padding: EdgeInsets.fromLTRB(20.0, 30.0, 20.0, 20.0),
      child: Column(
        children: [
          titleText,
          subTitle,
          ratings,
          iconList,
        ],
      ),
    );
    //...
  }
}

La columna izquierda esta colocada en un Container para restringir su ancho. Finalmente, la UI es construida con la fila entera (conteniendo la columna izquierda y la imagen) dentro de un Card.

La imagen de la Pavlova es de Pixabay y esta disponible bajo la licencia Creative Commons. Puedes insertar una imagen de la red usando Image.network pero, para este ejemplo, la imagen es guardada en un directorio de imágenes en el proyecto, añadido al fichero pubspec, y accedida usando Images.asset. Para más información, mira Añadir Assets e imágenes en Flutter.

body: Center(
  child: Container(
    margin: EdgeInsets.fromLTRB(0.0, 40.0, 0.0, 30.0),
    height: 600.0,
    child: Card(
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            width: 440.0,
            child: leftColumn,
          ),
          mainImage,
        ],
      ),
    ),
  ),
),

Código Dart: main.dart
Imágenes: imágenes
Pubspec: pubspec.yaml


Widgets de layout comunes

Flutter tiene una rica biblioteca de widgtes de layout, pero aquí estan algunos de los más comúnmente usados. La intención es hacerte avanzar lo más rápido posible, en lugar de abrumarte con una lista completa. Para información sobre otros widgets disponibles, te referimos a Visión general de Widgets, o usa la caja de búsqueda en la documentación de referencia de la API. También, las páginas de los widgets en la documentación de la API a menudo hace sugerencias sobre widgets similares que podrían adaptarse mejor a tus necesidades.

Los siguientes widgets entran en dos categorías: widgtes standard de la biblioteca de widgets, y widgets especializados de la biblioteca Material Components. Cualquier app puede usar la biblioteca de widgets pero solo las aplicaciones Material pueden usar la biblioteca Material Components.

Widgets Standard

  • Container
    Añade padding, margins, borders, background color, o otras decoraciones a un widget.
  • GridView
    Organiza los widgets como un grid scrollable.
  • ListView
    Organiza los widgets como una lista scrollable.
  • Stack
    Sobrepone los widgets encima de los otros.

Material Components

  • Card
    Organiza información relacionada en una caja con esquinas redondeadas que arroja una sombra.
  • ListTile
    Organiza hasta 3 líneas de texto, y opcionalmente iconos al principio o al final, dentro de una fila.

Container

Muchos layouts hacen un libre uso de los Containers para separar widgets con padding, o para añadir bordes o márgenes. Puedes cambiar el fondo del dispositivo colocando todo el layout dentro de un Container y cambiando su color o imagen de fondo.

Resumen de Container:

  • Añade padding, márgenes, bordes
  • Cambia color o imagen de fondo
  • Contiene un único widget hijo, pero este hijo puede ser un Row, Column, o ser la raíz de un árbol de widgets

un diagrama que muestra los márgenes, los bordes y el relleno, que rodean el contenido en un contenedor

Ejemplos con Container:

Además de los ejemplos más abajo, la mayoría de ejemplos en este tutorial usan Container. Puedes encontrar más ejemplos con Container en Flutter Gallery.

Este layout consiste en una columna y dos filas, cada una conteniendo 2 imágenes. Cada imagen usa un Container para añadir un borde redondeado gris y márgenes. El Column, que contiene las filas de imágenes, usa un Container para cambiar el color de fondo a gris claro.

Código Dart: main.dart, resumido abajo
Imágenes: imágenes
Pubspec: pubspec.yaml

una captura de pantalla mostrando 2 filas, cada una conteniendo 2 imágenes; las imágenes tienen un borde gris redondeado y un color de fondo gris claro

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {

    var container = Container(
      decoration: BoxDecoration(
        color: Colors.black26,
      ),
      child: Column(
        children: [
          Row(
            children: [
              Expanded(
                child: Container(
                  decoration: BoxDecoration(
                    border: Border.all(width: 10.0, color: Colors.black38),
                    borderRadius:
                        const BorderRadius.all(const Radius.circular(8.0)),
                  ),
                  margin: const EdgeInsets.all(4.0),
                  child: Image.asset('images/pic1.jpg'),
                ),
              ),
              Expanded(
                child: Container(
                  decoration: BoxDecoration(
                    border: Border.all(width: 10.0, color: Colors.black38),
                    borderRadius:
                        const BorderRadius.all(const Radius.circular(8.0)),
                  ),
                  margin: const EdgeInsets.all(4.0),
                  child: Image.asset('images/pic2.jpg'),
                ),
              ),
            ],
          ),
          // ...
          // Mira la definición de la segunda fila en GitHub:
          // https://raw.githubusercontent.com/flutter/website/master/src/_includes/code/layout/container/main.dart
        ],
      ),
    );
    //...
  }
}

GridView

Usa GridView para organizar los widgets en una lista bidimensional. GridView proporciona dos listas prefabricadas, o puedes construir tu propio grid personalizado. Cuando un GridView detecta que sus contenidos son demasiado largos para ajustarse al render box, este habilita el scroll automáticamente.

Resumen de GridView:

  • Organiza widgets en un grid
  • Detecta cuando el contenido de la columna sobrepasa el render box y automaticamente proporciona scroll
  • Construye tu propio grid personalizado, o usa uno de los grids proveidos:
    • GridView.count permite especificar el número de columnas
    • GridView.extent permite especificar el ancho máximo en pixels de un elemento

Ejemplos de GridView:

un grid de 3 columnas de fotos

Usa GridView.extent para crear un grid con elementos de 150 píxeles de ancho máximo.
Código Dart: main.dart, resumido abajo
Imágenes: imágenes
Pubspec: pubspec.yaml

un gri de 2 columnas con footers conteniendo títulos en un fondo parcialmente transparente

Usa GridView.count para crear un grid que tiene 2 elementos de ancho en modo portrait, y 3 elementos de ancho en modo landscape. Los títulos son creados asignando la propiedad footer para cada GridTile.
Código Dart: grid_list_demo.dart de la Flutter Gallery

// Las imágenes están guardadas con nombres pic1.jpg, pic2.jpg...pic30.jpg.
// El contructor List.generate permite crear de una manera sencilla
// una lista cuando los objetos tienen un patrón de nombrado predecible.
List<Container> _buildGridTileList(int count) {

  return List<Container>.generate(
      count,
      (int index) =>
          Container(child: Image.asset('images/pic${index+1}.jpg')));
}

Widget buildGrid() {
  return GridView.extent(
      maxCrossAxisExtent: 150.0,
      padding: const EdgeInsets.all(4.0),
      mainAxisSpacing: 4.0,
      crossAxisSpacing: 4.0,
      children: _buildGridTileList(30));
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: buildGrid(),
      ),
    );
  }
}

ListView

ListView, un widget similar a una columna, proporciona automáticamente cuando su contenido es demasiado largo para su render box.

Resumen de ListView:

  • Una columna especializada en organizar listas de cajas
  • Puede organizarse horizontal o verticalmente
  • Detecta cuando su contenido no puede ajustarse y proporciona la capacidad de hacer scroll
  • Menos configurable que un Column, pero más fácil de usar y dar soporte al scroll

Ejemplos de ListView:

un ListView conteniendo peliculas, teatros y restaurantes

Usa un ListView para mostrar una lista de negocios usando ListTiles. Un Divider separa los teatros de los restaurantes.
Código Dart: main.dart, resumido abajo
Iconos: Icons class
Pubspec: pubspec.yaml

un ListView conteniendo containing sombras de azul de la paleta de colores de Material Design

Usa un ListView para mostrar Colors de la paleta de Material Design para una familia de color en particular.
Código Dart: colors_demo.dart de la Flutter Gallery

List<Widget> list = <Widget>[
  ListTile(
    title: Text('CineArts at the Empire',
        style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20.0)),
    subtitle: Text('85 W Portal Ave'),
    leading: Icon(
      Icons.theaters,
      color: Colors.blue[500],
    ),
  ),
  ListTile(
    title: Text('The Castro Theater',
        style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20.0)),
    subtitle: Text('429 Castro St'),
    leading: Icon(
      Icons.theaters,
      color: Colors.blue[500],
    ),
  ),
  // ...
  // Mira el resto de la definición de la columna en GitHub:
  // https://raw.githubusercontent.com/flutter/website/master/src/_includes/code/layout/listview/main.dart
];

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // ...
      body: Center(
        child: ListView(
          children: list,
        ),
      ),
    );
  }
}

Stack

Usa Stack para organizar widgets encima de un widget base —quizás una imagen. Los widgets pueden solapar completa o parcialmente el widget base.

Resumen de Stack:

  • Usado para widgets que se sobreponen sobre otro widget
  • El primer widget en la lista de hijos es el widget base; los hijos subsiguientes son sobrepuestos encima de este widget base
  • El contenido de un Stack’s no puede hacer scroll
  • Puedes elegir recortar los hijos que excedan el render box

Ejemplos de Stack:

un avatar cirular conteniendo la etiqueta 'Mia B' en la posicion inferior derecha del círculo

Usa Stack para solapar un Container (que muestra su widget Text en un fondo negro tráslucido) encima de un Avatar Circular. Stack aplica un offset al texto usando la propiedad alignment y Alignments.
Código Dart: main.dart, resumido abajo
Imágen: imágenes
Pubspec: pubspec.yaml

una imagen con un gradiente gris sobre él encima del gradiente están las herramientas pintadas en blanco

Usa Stack para sobreponer un gradiente encima de la imagen. El gradiente asegura que el color de los iconos de la barra de herramientas se distinguen de la imagen.
Código Dart: contacts_demo.dart de la Flutter Gallery

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    var stack = Stack(
      alignment: const Alignment(0.6, 0.6),
      children: [
        CircleAvatar(
          backgroundImage: AssetImage('images/pic.jpg'),
          radius: 100.0,
        ),
        Container(
          decoration: BoxDecoration(
            color: Colors.black45,
          ),
          child: Text(
            'Mia B',
            style: TextStyle(
              fontSize: 20.0,
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          ),
        ),
      ],
    );
    // ...
  }
}

Card

Un Card, de la biblioteca Material Components, contiene porciones de información relacionada y puede ser compuesto por casi cualquier widget, pero es a menudo usado con ListTile. Card tiene un único hijo, pero este hijo puede ser una columna, fila, lista, grid, u otro widget que soporte múltiples hijos. Por defecto, un Card encoje su tamaño a 0 por 0 píxeles. Puedes usar SizedBox para restringir el tamaño de un Card.

En Flutter, un Card presenta esquinas ligeramente redondeadas y arroja una sombra, dándole un efecto 3D. Cambiando la propiedad elevation de Card te permite controlar el efecto de la sombra arrojada. Fijando la elevación a 24.0, por ejemplo, visualmente levanta el Card desde la superficie y provoca que la sombra se vuelva más dispersa. Para una lista de los valores soportados por elevation, mira Elevation y Shadows en las Material guidelines. Especificar un valor no soportado deshabilita completamente la sombra.

Resumen de Card:

  • Implementa un Material Design card
  • Usado para presentar porciones relacionadas de información
  • Acepta un único hijo, pero este hijo puede ser un Row, Column, u otro widget que sostenga una lista de hijos
  • Mostrado con esquinas redondeadas y sombra
  • El contenido de un Card no soporta scroll
  • De la biblioteca de Material Components

Ejemplos de Card:

un Card conteniendo 3 ListTiles

Un Card conteniendo 3 ListTiles y dimensionado envolviéndolo con un SizedBox. Un Divider separa el primer y el segundo ListTiles.

Código Dart: main.dart, resumido abajo
Iconos: Icons class
Pubspec: pubspec.yaml

un Card conteniendo una imagen ,un texto y botones bajo la imagen

Un Card conteniendo una imagen y texto.
Código Dart: cards_demo.dart de la Flutter Gallery

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    var card = SizedBox(
      height: 210.0,
      child: Card(
        child: Column(
          children: [
            ListTile(
              title: Text('1625 Main Street',
                  style: TextStyle(fontWeight: FontWeight.w500)),
              subtitle: Text('My City, CA 99984'),
              leading: Icon(
                Icons.restaurant_menu,
                color: Colors.blue[500],
              ),
            ),
            Divider(),
            ListTile(
              title: Text('(408) 555-1212',
                  style: TextStyle(fontWeight: FontWeight.w500)),
              leading: Icon(
                Icons.contact_phone,
                color: Colors.blue[500],
              ),
            ),
            ListTile(
              title: Text('costa@example.com'),
              leading: Icon(
                Icons.contact_mail,
                color: Colors.blue[500],
              ),
            ),
          ],
        ),
      ),
    );
  //...
}

ListTile

Usa ListTile, un widget de fila especializado de la librería Material Components, para crear fácilmente una fila conteniendo hasta 3 líneas de texto y opcionalmente iconos al principio o al final. ListTile es normalmente usado en Card o ListView, pero puede ser usado en cualquier parte.

Resumen de ListTile:

  • Una fila especializada que contiene hasta 3 líneas de texto y opcionalmente iconos
  • Menos configurable que un Row, pero más fácil de usar
  • De la biblioteca Material Components

ListTile examples:

un Card conteniendo 3 ListTiles

Un Card conteniendo 3 ListTiles.
Código Dart: Mira ejemplos de Card.

3 ListTiles, cada uno conteniendo un botón deplegble

Usa ListTile para listar 3 botones de tipo desplegable.
Código Dart: buttons_demo.dart de la Flutter Gallery


Recursos

Los siguientes recursos pueden ayudar cuando escribes código de layout.