MDC-102 Flutter: Estructura y diseño de Material

1. Introducción

logo_components_color_2x_web_96dp.png

Los componentes de Material (MDC) ayudan a los desarrolladores a implementar Material Design. MDC, creado por un equipo de ingenieros y diseñadores de UX en Google, cuenta con decenas de componentes de IU atractivos y funcionales, y está disponible para Android, iOS, la Web y Flutter.material.io/develop.

En el codelab MDC-101, usaste dos componentes de Material para crear una página de acceso: campos de texto y botones con efectos de tinta. Ahora vamos a agregar navegación, estructura y datos para expandir esta base.

Qué compilarás

En este codelab, compilarás una pantalla principal para una app llamada Shrine, una aplicación de comercio electrónico en la que se vende ropa y artículos para el hogar. Contiene lo siguiente:

  • Una barra superior de la app
  • Una lista de cuadrícula llena de productos

Android

iOS

App de comercio electrónico con una barra superior de la aplicación y una cuadrícula llena de productos

App de comercio electrónico con una barra superior de la aplicación y una cuadrícula llena de productos

Componentes y subsistemas de Flutter de Material en este codelab

  • Barra superior de la app
  • Cuadrículas
  • Tarjetas

¿Cómo calificarías tu nivel de experiencia con el desarrollo de Flutter?

Principiante Intermedio Avanzado

2. Configura tu entorno de desarrollo de Flutter

Para completar este lab, necesitas dos programas de software: el SDK de Flutter y un editor.

Puedes ejecutar el codelab con cualquiera de estos dispositivos o modalidades:

  • Un dispositivo físico Android o iOS conectado a tu computadora y configurado en el Modo de desarrollador
  • El simulador de iOS (requiere instalar las herramientas de Xcode)
  • Android Emulator (requiere configuración en Android Studio)
  • Un navegador (se requiere Chrome para la depuración)
  • Como una aplicación para computadoras que ejecuten Windows, Linux o macOS (debes desarrollarla en la plataforma donde tengas pensado realizar la implementación; por lo tanto, si quieres desarrollar una app de escritorio para Windows, debes desarrollarla en ese SO a fin de obtener acceso a la cadena de compilación correcta; encuentra detalles sobre los requisitos específicos del sistema operativo en docs.flutter.dev/desktop).

3. Descarga la app de partida del codelab

¿Vienes de MDC-101?

Si completaste MDC-101, tu código debería estar preparado para este codelab. Ve al paso Cómo agregar una barra superior de la app.

¿Empiezas de cero?

Descarga la app de inicio del codelab

La app de inicio se encuentra en el directorio material-components-flutter-codelabs-102-starter_and_101-complete/mdc_100_series.

… o clónalo desde GitHub

Para clonar este codelab desde GitHub, ejecuta los siguientes comandos:

git clone https://github.com/material-components/material-components-flutter-codelabs.git
cd material-components-flutter-codelabs/mdc_100_series
git checkout 102-starter_and_101-complete

Abre el proyecto y ejecuta la app

  1. Abre el proyecto en el editor que prefieras.
  2. Sigue las instrucciones para “ejecutar la app” en Get Started: Test drive en el editor que elegiste.

Listo Deberías ver la página de acceso a Shrine del codelab MDC-101 en tu dispositivo.

Android

iOS

página de acceso con los campos de nombre de usuario y contraseña, botones de cancelación y siguiente

página de acceso con los campos de nombre de usuario y contraseña, botones de cancelación y siguiente

Ahora que la pantalla de acceso se ve bien, vamos a propagar la app con algunos productos.

4. Cómo agregar una barra superior de la app

En este momento, si haces clic en el botón "Next", podrás ver la pantalla principal que dice "You did it!". ¡Muy bien! Sin embargo, ahora el usuario no tiene ninguna acción que realizar o que no sepa en qué parte de la app se encuentra. Para ayudarlo, es momento de agregar la navegación.

Material Design ofrece patrones de navegación que garantizan un alto grado de usabilidad. Uno de los componentes más visibles es la barra superior de la app.

Para posibilitar la navegación y brindar a los usuarios acceso rápido a otras acciones, agregaremos una barra superior de la app.

Cómo agregar un widget de AppBar

En home.dart, agrega una AppBar a Scaffold y quita el elemento const destacado:

return const Scaffold(
  // TODO: Add app bar (102)
  appBar: AppBar(
    // TODO: Add buttons and title (102)
  ),

Agregar la AppBar al campo appBar: de Scaffold nos brinda un diseño perfecto gratis, con AppBar en la parte superior de la página y el cuerpo debajo.

Guarda el proyecto. Cuando se actualice la app de Shrine, haz clic en Next para ver la pantalla principal.

Android

iOS

que dice "¡Lo lograste!"

que dice "¡Lo lograste!"

AppBar se ve muy bien, pero necesita un título.

Cómo agregar un widget de texto

En home.dart, agrega un título a AppBar:

// TODO: Add app bar (102)
  appBar: AppBar(
    // TODO: Add buttons and title (102)
    title: const Text('SHRINE'),
    // TODO: Add trailing buttons (102)

Guarda el proyecto.

Android

iOS

una barra de la app con el título Shrine

una barra de la app con el título Shrine

Muchas barras de la app tienen un botón junto al título. Agreguemos un ícono de menú a nuestra app.

Cómo agregar un IconButton inicial

Mientras estás en home.dart, configura un IconButton para el campo leading: de AppBar. Colócalo antes del campo title: para imitar el orden de principal a final:

    // TODO: Add buttons and title (102)
    leading: IconButton(
      icon: const Icon(
        Icons.menu,
        semanticLabel: 'menu',
      ),
      onPressed: () {
        print('Menu button');
      },
    ),

Guarda el proyecto.

Android

iOS

una barra de la app con el título Shrine y un ícono de menú de hamburguesas

una barra de la app con el título Shrine y un ícono de menú de hamburguesas

El ícono de menú (también conocido como "ícono de opciones") aparece justo donde lo esperarías.

También puedes agregar botones en el extremo final del título. En Flutter, esto se denomina "acciones".

Cómo agregar acciones

Hay espacio para dos IconButtons más.

Agrégalos a la instancia de AppBar después del título:

// TODO: Add trailing buttons (102)
actions: <Widget>[
  IconButton(
    icon: const Icon(
      Icons.search,
      semanticLabel: 'search',
    ),
    onPressed: () {
      print('Search button');
    },
  ),
  IconButton(
    icon: const Icon(
      Icons.tune,
      semanticLabel: 'filter',
    ),
    onPressed: () {
      print('Filter button');
    },
  ),
],

Guarda el proyecto. La pantalla principal debería verse de la siguiente manera:

Android

iOS

una barra de la app con el título como un ícono de menú, una hamburguesa y los íconos de búsqueda y personalización finales

una barra de la app con el título como un ícono de menú, una hamburguesa y los íconos de búsqueda y personalización finales

Ahora la app tiene un botón inicial, un título y dos acciones a la derecha. La barra de la app también muestra el valor de elevación mediante una sombra sutil que indica que se encuentra en una capa diferente a la del contenido.

5. Cómo agregar una tarjeta en una cuadrícula

Ahora que nuestra app tiene cierta estructura, coloquemos el contenido en tarjetas para organizarlo.

Cómo agregar una GridView

Comencemos por agregar una tarjeta debajo de la barra superior de la app. El widget de tarjeta por sí solo no cuenta con información suficiente para colocarse donde podamos verlo, por lo que deberíamos encapsularlo en un widget de GridView.

Reemplaza el centro en el cuerpo de Scaffold con una GridView:

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  // TODO: Build a grid of cards (102)
  children: <Widget>[Card()],
),

Vamos a desglosar ese código. GridView invoca al constructor count(), ya que el número de elementos que muestra es contable y no infinito. Pero necesita más información para definir su diseño.

crossAxisCount: especifica la cantidad de elementos. Queremos 2 columnas.

El campo padding: proporciona espacio en los 4 lados de GridView. Por supuesto, no puedes ver el relleno en los extremos final o inferior porque todavía no hay elementos secundarios de GridView junto a ellos.

El campo childAspectRatio: identifica el tamaño de los elementos según una relación de aspecto (ancho sobre altura).

De forma predeterminada, GridView crea mosaicos que tienen el mismo tamaño.

Tenemos una tarjeta, pero está vacía. Vamos a agregarle widgets secundarios.

Cómo diseñar el contenido

Las tarjetas deben tener regiones para una imagen, un título y un texto secundario.

Actualiza los elementos secundarios de GridView:

// TODO: Build a grid of cards (102)
children: <Widget>[
  Card(
    clipBehavior: Clip.antiAlias,
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        AspectRatio(
          aspectRatio: 18.0 / 11.0,
          child: Image.asset('assets/diamond.png'),
        ),
        Padding(
          padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text('Title'),
              const SizedBox(height: 8.0),
              Text('Secondary Text'),
            ],
          ),
        ),
      ],
    ),
  )
],

Este código agrega un widget de columna que se usa para distribuir los widgets secundarios de forma vertical.

crossAxisAlignment: field especifica CrossAxisAlignment.start, que significa "alinear el texto al extremo inicial".

El widget de AspectRatio decide qué forma toma la imagen sin importar el tipo de imagen que se proporciona.

El relleno acerca un poco el texto desde el borde.

Los dos widgets de texto se apilan verticalmente con 8 puntos de espacio vacío entre ellos (SizedBox). Creamos otra columna para colocarlos dentro del relleno.

Guarda el proyecto.

Android

iOS

un solo elemento con una imagen, un título y texto secundario

un solo elemento con una imagen, un título y texto secundario

En esta vista previa, puedes ver que la tarjeta está insertada desde el extremo, con esquinas redondeadas y una sombra (que expresa la elevación de la tarjeta). Toda la forma se denomina "contenedor" en Material. (No debe confundirse con la clase de widget llamada Container).

Las tarjetas suelen mostrarse en una colección con otras tarjetas. Vamos a distribuirlas como una colección en una cuadrícula.

6. Cómo crear una colección de tarjetas

Cuando hay varias tarjetas presentes en una pantalla, se agrupan en una o más colecciones. Las tarjetas en una colección son coplanarias, lo que significa que comparten la misma elevación en reposo que las otras (a menos que se recojan o se arrastren, pero no lo haremos aquí).

Cómo multiplicar la tarjeta en una colección

En este momento, nuestra tarjeta está construida en línea con el campo children: de GridView. Hay mucho código anidado que puede ser difícil de leer. Vayamos a una función que pueda generar tantas tarjetas vacías como queramos y mostrar una lista de tarjetas.

Crea una nueva función privada encima de la función build() (recuerda que las funciones que comienzan con un guion bajo son API privadas):

// TODO: Make a collection of cards (102)
List<Card> _buildGridCards(int count) {
  List<Card> cards = List.generate(
    count,
    (int index) {
      return Card(
        clipBehavior: Clip.antiAlias,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            AspectRatio(
              aspectRatio: 18.0 / 11.0,
              child: Image.asset('assets/diamond.png'),
            ),
            Padding(
              padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: const <Widget>[
                  Text('Title'),
                  SizedBox(height: 8.0),
                  Text('Secondary Text'),
                ],
              ),
            ),
          ],
        ),
      );
    },
  );
  return cards;
}

Asigna las tarjetas generadas al campo children de GridView. Recuerda reemplazar todo en la GridView con este código nuevo:

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  children: _buildGridCards(10) // Replace
),

Guarda el proyecto.

Android

iOS

una cuadrícula de elementos con una imagen, un título y un texto secundario

una cuadrícula de elementos con una imagen, un título y un texto secundario

Las tarjetas están allí, pero todavía no muestran nada. Es el momento de agregar datos de productos.

Cómo agregar datos de productos

La app tiene algunos productos con imágenes, nombres y precios. Agreguemos eso a los widgets que ya tengamos en la tarjeta.

Luego, en home.dart, importa un paquete nuevo y algunos archivos que proporcionamos para un modelo de datos:

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

import 'model/product.dart';
import 'model/products_repository.dart';

Por último, cambia _buildGridCards() para obtener la información del producto y, luego, usa esos datos en las tarjetas:

// TODO: Make a collection of cards (102)

// Replace this entire method
List<Card> _buildGridCards(BuildContext context) {
  List<Product> products = ProductsRepository.loadProducts(Category.all);

  if (products.isEmpty) {
    return const <Card>[];
  }

  final ThemeData theme = Theme.of(context);
  final NumberFormat formatter = NumberFormat.simpleCurrency(
      locale: Localizations.localeOf(context).toString());

  return products.map((product) {
    return Card(
      clipBehavior: Clip.antiAlias,
      // TODO: Adjust card heights (103)
      child: Column(
        // TODO: Center items on the card (103)
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          AspectRatio(
            aspectRatio: 18 / 11,
            child: Image.asset(
              product.assetName,
              package: product.assetPackage,
             // TODO: Adjust the box size (102)
            ),
          ),
          Expanded(
            child: Padding(
              padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
              child: Column(
               // TODO: Align labels to the bottom and center (103)
               crossAxisAlignment: CrossAxisAlignment.start,
                // TODO: Change innermost Column (103)
                children: <Widget>[
                 // TODO: Handle overflowing labels (103)
                 Text(
                    product.name,
                    style: theme.textTheme.titleLarge,
                    maxLines: 1,
                  ),
                  const SizedBox(height: 8.0),
                  Text(
                    formatter.format(product.price),
                    style: theme.textTheme.titleSmall,
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }).toList();
}

NOTA: Aún no se compilará ni se ejecutará. Queda un cambio más.

Además, cambia la función build() para pasar BuildContext a _buildGridCards() antes de intentar compilar el código:

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  children: _buildGridCards(context) // Changed code
),

Reinicia la app en caliente.

Android

iOS

una cuadrícula de artículos con una imagen, un título de producto y un precio

una cuadrícula de artículos con una imagen, un título de producto y un precio

Es posible que notes que no agregamos ningún espacio vertical entre las tarjetas. Eso se debe a que, de forma predeterminada, tienen 4 puntos de margen en la parte superior e inferior.

Guarda el proyecto.

Se muestran los datos del producto, pero las imágenes tienen espacio adicional. De forma predeterminada, las imágenes se dibujan con un BoxFit de .scaleDown (en este caso). Cambiemos eso a .fitWidth para que se acerquen un poco y se quite el espacio en blanco adicional.

Agrega un campo fit: a la imagen con un valor de BoxFit.fitWidth:

  // TODO: Adjust the box size (102)
  fit: BoxFit.fitWidth,

Android

iOS

una cuadrícula de artículos con una imagen recortada, un título de producto y el precio

una cuadrícula de artículos con una imagen recortada, un título de producto y el precio

Nuestros productos ya se muestran perfectamente en la app.

7. Felicitaciones

Nuestra app tiene un flujo básico que lleva al usuario de la pantalla de acceso a una pantalla principal en la que se pueden ver los productos. Con unas pocas líneas de código, agregamos una barra superior de la app (con un título y tres botones) y tarjetas (para presentar el contenido de la app). Ahora, la pantalla principal es simple y funcional, y tiene una estructura básica y contenido de acción.

Próximos pasos

Con la barra superior de la app, la tarjeta, el campo de texto y el botón, usamos cuatro componentes principales de la biblioteca de Material de Flutter. Para obtener más información, visita el catálogo de widgets de componentes de Material.

Aunque funciona en su totalidad, nuestra app aún no expresa ninguna marca ni punto de vista en particular. En MDC-103: Temas de Material Design con color, forma, elevación y tipo, personalizaremos el estilo de estos componentes para expresar una marca moderna y llamativa.

Pude completar este codelab con una cantidad de tiempo y esfuerzo razonables.

Totalmente de acuerdo De acuerdo Neutral En desacuerdo Totalmente en desacuerdo

Me gustaría seguir usando los componentes de Material en el futuro.

Totalmente de acuerdo De acuerdo Neutral En desacuerdo Totalmente en desacuerdo