Tutorial de layout

Esta es una guía para construir layouts en Flutter. Construirás el layout para la siguiente aplicación:

The finished app
The finished app

Esta guía da un paso atrás para explicar el enfoque de Flutter sobre el layout y muestra cómo colocar un único widget en la pantalla. Después de una discusión sobre cómo colocar los widgets horizontalmente y verticalmente, se tratan algunos de los widgets de layout más comunes.

Si deseas una visión general del mecanismo de layout, comienza con el enfoque de Flutter sobre el layout.

Paso 0: Crear el código base de la aplicación

Asegúrate de haber configurado tu entorno y luego haz lo siguiente:

  1. Crea una aplicación básica Flutter “Hello World”.
  2. Cambia el título de la barra de aplicaciones y el título de la aplicación como sigue:

    {codelabs/startup_namer/step1_base → layout/base}/lib/main.dart
    @@ -6,10 +6,10 @@
    6
    6
    @override
    7
    7
    Widget build(BuildContext context) {
    8
    8
    return MaterialApp(
    9
    - title: 'Welcome to Flutter',
    9
    + title: 'Flutter layout demo',
    10
    10
    home: Scaffold(
    11
    11
    appBar: AppBar(
    12
    - title: Text('Welcome to Flutter'),
    12
    + title: Text('Flutter layout demo'),
    13
    13
    ),
    14
    14
    body: Center(
    15
    15
    child: Text('Hello World'),

Paso 1: Diagrama del layout

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

  • Identificar las filas y columnas.
  • ¿El layout incluye una cuadrícula?
  • ¿Hay elementos que se superponen?
  • ¿Necesita pestañas la interfaz de usuario?
  • Observe las áreas que requieren alineación, padding o bordes.

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

Column elements (circled in red)
Column elements (circled in red)

A continuación, diagrama cada fila. La primera fila, llamada sección Título, tiene 3 hijos: una columna de texto, un icono de estrella y un número. Su primer hijo, la columna, contiene 2 líneas de texto. Esa primera columna ocupa mucho espacio, por lo que debe estar envuelta en un widget Expanded.

Title section

La segunda fila, llamada sección Botón, también tiene 3 hijos: cada hijo es una columna que contiene un icono y un texto.

Button section

Una vez que se ha diagramado el layout, lo más fácil es adoptar un enfoque ascendente para implementarlo. Para minimizar la confusión visual del código de layout profundamente anidado, coloca parte de la implementación en variables y funciones.

Paso 2: Implementar la fila de título

Primero, construirás la columna izquierda en la sección de título. Agrega el siguiente código en la parte superior del método build() de la clase MyApp:

lib/main.dart (titleSection)
Widget titleSection = Container(
  padding: const EdgeInsets.all(32),
  child: Row(
    children: [
      Expanded(
        /*1*/
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            /*2*/
            Container(
              padding: const EdgeInsets.only(bottom: 8),
              child: Text(
                'Oeschinen Lake Campground',
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
            Text(
              'Kandersteg, Switzerland',
              style: TextStyle(
                color: Colors.grey[500],
              ),
            ),
          ],
        ),
      ),
      /*3*/
      Icon(
        Icons.star,
        color: Colors.red[500],
      ),
      Text('41'),
    ],
  ),
);
  1. Al colocar una columna dentro de un widget Expanded, se estira la columna para utilizar todo el espacio libre que queda en la fila. Al establecer la propiedad crossAxisAlignment a CrossAxisAlignment.start se posiciona la columna al principio de la fila.
  2. Poner la primera fila de texto dentro de un Container te permite añadir padding. El segundo hijo en la Columna, también texto, se visualiza como gris.
  3. Los dos últimos elementos de la fila del título son un icono de estrella, pintado de rojo, y el texto “41”. Toda la fila está en un Container y con padding a lo largo de cada borde por 32 píxeles.

Añade la sección de título al cuerpo de la aplicación de esta manera:

{../base → step2}/lib/main.dart
@@ -8,11 +46,13 @@
8
46
return MaterialApp(
9
47
title: 'Flutter layout demo',
10
48
home: Scaffold(
11
49
appBar: AppBar(
12
50
title: Text('Flutter layout demo'),
13
51
),
14
- body: Center(
15
- child: Text('Hello World'),
52
+ body: Column(
53
+ children: [
54
+ titleSection,
55
+ ],
16
56
),
17
57
),
18
58
);

Paso 3: Implementar la fila de botones

La sección de botones contiene 3 columnas que utilizan la misma disposición: un icono sobre una línea de texto. Las columnas de esta fila están espaciadas uniformemente, y el texto y los iconos están pintados con el color primario.

Como el código para construir cada columna es casi idéntico, crea un método de ayuda privado llamado buildButtonColumn(), que toma un color, un Icono y Texto, y devuelve una columna con sus widgets pintados con el color dado.

lib/main.dart (_buildButtonColumn)
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // ···
  }

  Column _buildButtonColumn(Color color, IconData icon, String label) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, color: color),
        Container(
          margin: const EdgeInsets.only(top: 8),
          child: Text(
            label,
            style: TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.w400,
              color: color,
            ),
          ),
        ),
      ],
    );
  }
}

La función añade el icono directamente a la columna. El texto se encuentra dentro de un Container con un margen superior, separando el texto del icono.

Construye la fila que contiene estas columnas llamando a la función y pasando el color, Icon, y el texto específico de esa columna. Alinea las columnas a lo largo del eje principal utilizando MainAxisAlignment.spaceEvenly para organizar el espacio libre uniformemente antes, entre y después de cada columna. Agrega el siguiente código justo debajo de la declaración titleSection dentro del método build():

lib/main.dart (buttonSection)
Color color = Theme.of(context).primaryColor;

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

Añade la sección de botones al body:

{step2 → step3}/lib/main.dart
@@ -46,3 +59,3 @@
46
59
return MaterialApp(
47
60
title: 'Flutter layout demo',
48
61
home: Scaffold(
@@ -52,8 +65,9 @@
52
65
body: Column(
53
66
children: [
54
67
titleSection,
68
+ buttonSection,
55
69
],
56
70
),
57
71
),
58
72
);
59
73
}

Paso 4: Implementar la sección de texto

Define la sección de texto como una variable. Pon el texto en un Container y agrega padding a lo largo de cada borde. Añade el siguiente código justo debajo de la declaración buttonSection:

lib/main.dart (textSection)
Widget textSection = Container(
  padding: const EdgeInsets.all(32),
  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,
  ),
);

Al establecer softwrap a true, las líneas de texto rellenarán el ancho de la columna antes de ajustarla al límite de una palabra.

Añade la sección de texto al body:

{step3 → step4}/lib/main.dart
@@ -59,3 +72,3 @@
59
72
return MaterialApp(
60
73
title: 'Flutter layout demo',
61
74
home: Scaffold(
@@ -66,6 +79,7 @@
66
79
children: [
67
80
titleSection,
68
81
buttonSection,
82
+ textSection,
69
83
],
70
84
),
71
85
),

Paso 5: Implementar la sección de imagen

Tres de los cuatro elementos de la columna están ahora completos, dejando sólo la imagen. Añade el archivo de imagen al ejemplo:

  • Crea un directorio images en la parte superior del proyecto.
  • Añade lake.jpg.

  • Actualice el archivo pubspec.yaml para incluir una etiqueta assets. Esto hace que la imagen esté disponible para tu código.

    {step4 → step5}/pubspec.yaml
    @@ -17,3 +17,5 @@
    17
    17
    flutter:
    18
    18
    uses-material-design: true
    19
    + assets:
    20
    + - images/lake.jpg

Ahora puedes referenciar la imagen de tu código:

{step4 → step5}/lib/main.dart
@@ -77,6 +77,12 @@
77
77
),
78
78
body: Column(
79
79
children: [
80
+ Image.asset(
81
+ 'images/lake.jpg',
82
+ width: 600,
83
+ height: 240,
84
+ fit: BoxFit.cover,
85
+ ),
80
86
titleSection,
81
87
buttonSection,
82
88
textSection,

BoxFit.cover le dice al framework que la imagen debe ser lo más pequeña posible pero que debe cubrir toda su caja de render.

Paso 6: El toque final

En este paso final, ordena todos los elementos en un ListView, en lugar de una Column, porque un ListView soporta el desplazamiento del body de la aplicación cuando la aplicación se ejecuta en un dispositivo pequeño.

{step5 → step6}/lib/main.dart
@@ -72,13 +77,13 @@
72
77
return MaterialApp(
73
78
title: 'Flutter layout demo',
74
79
home: Scaffold(
75
80
appBar: AppBar(
76
81
title: Text('Flutter layout demo'),
77
82
),
78
- body: Column(
83
+ body: ListView(
79
84
children: [
80
85
Image.asset(
81
86
'images/lake.jpg',
82
87
width: 600,
83
88
height: 240,
84
89
fit: BoxFit.cover,

Dart code: main.dart
Image: images
Pubspec: pubspec.yaml

¡Eso es todo! Cuando recargas la aplicación en caliente, deberías ver el mismo layout de la aplicación como la de la captura de pantalla en la parte superior de esta página.

Puedes añadir interactividad a este layout siguiendo el enlace Agregando interactividad a tu aplicación Flutter.