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

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

Componentes de MDC 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

Antes de comenzar

A fin de comenzar a desarrollar apps para dispositivos móviles con Flutter, sigue estos pasos:

  1. Descarga e instala el SDK de Flutter.
  2. Actualiza tu PATH con el SDK de Flutter.
  3. Instala Android Studio con los complementos de Flutter y Dart, o tu editor favorito.
  4. Instala un emulador de Android o un simulador de iOS (requiere una Mac con Xcode), o usa un dispositivo físico.

Para obtener más información sobre la instalación de Flutter, consulta Cómo comenzar: instalación. Para configurar un editor, consulta Cómo comenzar: configurar un editor. Cuando instales un emulador de Android, podrás usar las opciones predeterminadas, como un teléfono Pixel 3 con la imagen del sistema más reciente. Se recomienda, pero no es necesario, habilitar la aceleración de VM. Una vez que hayas completado los 4 pasos anteriores, podrás volver al codelab. Para completar este codelab, solo debes instalar Flutter en una plataforma (Android o iOS).

Asegúrate de que el SDK de Flutter esté en el estado correcto

Antes de continuar con este codelab, asegúrate de que el SDK esté en el estado correcto. Si el SDK de Flutter se instaló anteriormente, usa flutter upgrade para asegurarte de que el SDK esté en el estado más reciente.

 flutter upgrade

Si ejecutas flutter upgrade, se ejecutará automáticamente flutter doctor.. Si es una instalación nueva de Flutter y no necesitas actualizar nada, ejecuta flutter doctor de forma manual. Informará si hay dependencias que necesitas instalar para completar la configuración. Puedes ignorar las marcas de verificación que no sean relevantes para ti (por ejemplo, Xcode si no deseas desarrollar para iOS).

 flutter doctor

Preguntas frecuentes

¿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

Descargar la app de inicio

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

Cómo configurar tu proyecto

En las siguientes instrucciones, se da por sentado que usas Android Studio (IntelliJ).

Cómo abrir el proyecto

1. Abre Android Studio.

2. Si ves la pantalla de bienvenida, haz clic en Open an existing Android Studio project.

3. Navega al directorio material-components-flutter-codelabs/mdc_100_series y haz clic en Open. Se debería abrir el proyecto. Puedes ignorar cualquier error que veas en Dart Analysis hasta que hayas compilado el proyecto una vez.

4. Si se te solicita, haz lo siguiente:

  • Instala cualquier actualización de plataforma y complemento o FlutterRunConfigurationType.
  • Si el SDK de Dart o Flutter no está configurado, establece la ruta del SDK de Flutter para el complemento de Flutter.
  • Configura los frameworks de Android.
  • Haz clic en "Get dependencies" o "Run 'flutter packages get'".

Luego, reinicia Android Studio.

Cómo ejecutar la app de inicio

En las siguientes instrucciones, se da por sentado que realizas la prueba en un dispositivo o emulador de Android, pero también puedes realizar pruebas en un dispositivo o simulador de iOS si tienes instalado Xcode.

1. Selecciona el dispositivo o emulador. Si el emulador de Android aún no se está ejecutando, selecciona Tools -> Android -> AVD Manager para crear un dispositivo virtual e iniciar el emulador. Si ya existe un AVD, puedes iniciar el emulador directamente desde el selector de dispositivos de Android Studio, como se muestra en el siguiente paso. (Para el simulador de iOS, si aún no se está ejecutando, selecciona Flutter Device Selection -> Open iOS Simulator para iniciarlo en la máquina de desarrollo).

2. Inicia tu app de Flutter:

  • Busca el menú desplegable "Device Selection" de Flutter en la parte superior de la pantalla del editor y selecciona el dispositivo (por ejemplo, iPhone SE o Android SDK built for <versión>).
  • Presiona el ícono de Play ().

Listo. Deberías ver la página de acceso de Shrine desde el codelab MDC-101 en el simulador o el emulador.

Android

iOS

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

En este momento, si haces clic en el botón "Next", podrás ver la pantalla principal que dice "You did it!". ¡Muy bien! Pero ahora nuestro usuario no tiene ninguna acción para realizar ni sabe en qué parte de la app está. Para solucionarlo, 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:

  // 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, y AppBar se mantiene en la parte superior de la página con el cuerpo debajo.

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

Android

iOS

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: Text('SHRINE'),
    // TODO: Add trailing buttons (102)

Guarda el proyecto.

Android

iOS

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: Icon(
        Icons.menu,
        semanticLabel: 'menu',
      ),
      onPressed: () {
        print('Menu button');
      },
    ),

Guarda el proyecto.

Android

iOS

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: Icon(
      Icons.search,
      semanticLabel: 'search',
    ),
    onPressed: () {
      print('Search button');
    },
  ),
  IconButton(
    icon: Icon(
      Icons.tune,
      semanticLabel: 'filter',
    ),
    onPressed: () {
      print('Filter button');
    },
  ),
],

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

Android

iOS

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.

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: 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 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.

Sumando todo lo anterior, GridView calcula el ancho de cada elemento secundario de esta manera: ([width of the entire grid] - [left padding] - [right padding]) / number of columns. Usando los valores que tenemos: ([width of the entire grid] - 16 - 16) / 2.

La altura se calcula a partir del ancho, aplicando la relación de aspecto: ([width of the entire grid] - 16 - 16) / 2 * 9 / 8. Intercambiamos 8 y 9 porque comenzamos con el ancho y calculamos la altura, no viceversa.

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: EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text('Title'),
              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

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.

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) => 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: EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <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: EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  children: _buildGridCards(10) // Replace
),

Guarda el proyecto:

Android

iOS

Las tarjetas están allí, pero todavía no muestran nada. Es el momento de agregar algunos 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/products_repository.dart';
import 'model/product.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 == null || 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: 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.headline6,
                    maxLines: 1,
                  ),
                  SizedBox(height: 8.0),
                  Text(
                    formatter.format(product.price),
                    style: theme.textTheme.subtitle2,
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }).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: EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  children: _buildGridCards(context) // Changed code
),

Android

iOS

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 relleno 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

Nuestros productos ya se muestran perfectamente en la app.

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, las tarjetas, los campos de texto y los botones, usamos cuatro elementos principales de la biblioteca de MDC-Flutter. Puedes visitar el Catálogo de widgets de Flutter para explorar aún más componentes.

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.

Siguiente codelab

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