MDC-103 Flutter: Temas de Material con color, forma, elevación y tipo

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.

Ahora más que nunca puedes usar MDC para personalizar el estilo único de tus apps. La expansión reciente de Material Design les proporciona a los desarrolladores y diseñadores más flexibilidad para expresar la marca de sus productos.

En los codelabs MDC-101 y MDC-102, usaste componentes de Material (MDC) para compilar los aspectos básicos de una app de comercio electrónico llamada Shrine, en la que se vende ropa y artículos para el hogar. Esta app incluye un flujo de usuarios que comienza con una pantalla de acceso y, luego, dirige al usuario a una pantalla principal en la que se muestran productos.

Qué compilarás

En este codelab, personalizarás la app de Shrine mediante los siguientes elementos:

  • Color
  • Tipografía
  • Elevación
  • Forma
  • Diseño

Android

iOS

Página de acceso de Shrine, con tema café y rosa

Página de acceso de Shrine, con tema café y rosa

Página de productos de Shrine (con tema café y rosa) con una barra superior de la app y una cuadrícula asimétrica llena de productos por la que es posible desplazarse horizontalmente

Página de productos de Shrine (con tema café y rosa) con una barra superior de la app y una cuadrícula asimétrica llena de productos por la que es posible desplazarse horizontalmente

Componentes y subsistemas de MDC-Flutter de este codelab

  • Temas
  • Tipografía
  • Elevación
  • Lista de imágenes

¿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-102?

Si completaste MDC-102, tu código debería estar listo para este codelab. Ve al paso Cambia los colores.

¿Empiezas de cero?

Descarga la app de inicio del codelab

La app de inicio se encuentra en el directorio material-components-flutter-codelabs-103-starter_and_102-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 103-starter_and_102-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 de Shrine de los codelabs anteriores en tu dispositivo.

Android

iOS

Página de acceso de Shrine sin tema

Página de acceso de Shrine sin tema

Haz clic en Siguiente para ver la página principal del codelab anterior.

Android

iOS

Página de la cuadrícula de productos de Shrine sin tema

Página de la cuadrícula de productos de Shrine sin tema

4. Cambia los colores

Se creó un esquema de colores que representa la marca de Shrine, y al diseñador le gustaría que lo implementes en toda la app de Shrine.

Para comenzar, importa esos colores al proyecto.

Crea colors.dart

Crea un archivo Dart nuevo llamado colors.dart en lib. Importa componentes de Material y agrega valores const de color, como se muestra a continuación:

import 'package:flutter/material.dart';

const kShrinePink50 = Color(0xFFFEEAE6);
const kShrinePink100 = Color(0xFFFEDBD0);
const kShrinePink300 = Color(0xFFFBB8AC);
const kShrinePink400 = Color(0xFFEAA4A4);

const kShrineBrown900 = Color(0xFF442B2D);

const kShrineErrorRed = Color(0xFFC5032B);

const kShrineSurfaceWhite = Color(0xFFFFFBFA);
const kShrineBackgroundWhite = Colors.white;

Paleta de colores personalizados

Un diseñador creó este tema con colores personalizados (consulta la imagen más abajo). Contiene colores seleccionados de la marca de Shrine que se aplicaron en Material Theme Editor, donde se expandieron para crear una paleta más completa. (Estos colores no provienen de las paletas de colores de Material de 2014).

En Material Theme Editor, se organizan por matiz y están etiquetados de forma numérica, incluidas las etiquetas 50, 100, 200, y así sucesivamente hasta el 900, de cada color. Shrine solo usa los matices 50, 100 y 300 de la muestra rosa y el 900 de la muestra marrón.

b9170eb94fd3b106.jpeg f8b4b97f898f154e.png

Cada parámetro con color de un widget se asigna a un color de estos esquemas. Por ejemplo, el color de las decoraciones de un campo de texto debería ser el primario del tema cuando se ingresa una entrada en el campo. Si ese color no es accesible (fácil de ver contra el fondo), usa PrimaryVariant en su lugar.

Las variaciones se crearon para los lineamientos de Material de 2014 y siguen disponibles en los lineamientos actuales (artículo del sistema de colores) y en MDC-Flutter. Para acceder a ellas en el código, llama al color de base y, luego, al matiz (por lo general un valor de centena). Por ejemplo, el comando Colors.pink[400] recupera el rosa 400.

Es perfectamente aceptable que uses estas paletas para tus diseños y tu código. Si tu marca ya tiene colores específicos, puedes generar tus propias paletas armoniosas con la herramienta de generación de paletas o con Material Theme Editor.

Ahora que ya tienes los colores que deseas usar, puedes aplicarlos a la IU. Para ello, debes configurar los valores de un widget ThemeData que aplicarás a la instancia de MaterialApp en la parte superior de la jerarquía de widgets.

Personaliza ThemeData.light()

Flutter incluye algunos temas integrados, como el tema claro. En lugar de crear un widget ThemeData desde cero, copiarás el tema claro y cambiarás los valores a fin de personalizarlos para la app.

Importa colors.dart en app.dart.

import 'colors.dart';

Luego, agrega lo siguiente a app.dart fuera del alcance de la clase ShrineApp:

// TODO: Build a Shrine Theme (103)
final ThemeData _kShrineTheme = _buildShrineTheme();

ThemeData _buildShrineTheme() {
  final ThemeData base = ThemeData.light();
  return base.copyWith(
    colorScheme: base.colorScheme.copyWith(
      primary: kShrinePink100,
      onPrimary: kShrineBrown900,
      secondary: kShrineBrown900,
      error: kShrineErrorRed,
    ),
    // TODO: Add the text themes (103)
    // TODO: Add the icon themes (103)
    // TODO: Decorate the inputs (103)
  );
}

Ahora, configura theme: al final de la función build() de ShrineApp (en el widget MaterialApp) para que sea el nuevo tema.

  // TODO: Add a theme (103)
  theme: _kShrineTheme, // New code

Guarda el proyecto. La pantalla de acceso debería verse de esta manera:

Android

iOS

Página de acceso de Shrine, con tema café y rosa

Página de acceso de Shrine, con tema café y rosa

Además, la pantalla principal debería tener el siguiente aspecto:

Android

iOS

Página de la cuadrícula de productos de Shrine, con tema café y rosa

Página de la cuadrícula de productos de Shrine, con tema café y rosa

5. Modifica los estilos de tipografía y etiquetas

Además de los cambios de color, el diseñador proporcionó una tipografía específica para usar. ThemeData de Flutter incluye 3 temas de texto. Cada tema de texto es una colección de estilos de texto, como "encabezado" y "título". Usarás algunos estilos para la app y cambiarás algunos valores.

Personaliza el tema de texto

A fin de importar fuentes al proyecto, las debes agregar al archivo pubspec.yaml.

En pubspec.yaml, agrega lo siguiente inmediatamente después de la etiqueta flutter::

  # TODO: Insert Fonts (103)
  fonts:
    - family: Rubik
      fonts:
        - asset: fonts/Rubik-Regular.ttf
        - asset: fonts/Rubik-Medium.ttf
          weight: 500

Ahora puedes acceder a la fuente Rubik y usarla.

Solución de problemas con el archivo pubspec

Es posible que se produzcan errores cuando ejecutes pub get si cortas y pegas la declaración anterior. En ese caso, quita el espacio en blanco inicial y reemplázalo con espacios que usen sangría de 2 espacios. Dos espacios antes de lo siguiente:

fonts:

Cuatro espacios antes de lo siguiente:

family: Rubik

Así sucesivamente.

Si aparece el mensaje Mapping values are not allowed here (aquí no se admiten valores de asignación), revisa la sangría de la línea que tiene el problema y la de las líneas superiores.

En login.dart, cambia lo siguiente dentro de Column():

Column(
  children: <Widget>[
    Image.asset('assets/diamond.png'),
    const SizedBox(height: 16.0),
    Text(
      'SHRINE',
      style: Theme.of(context).textTheme.headline5,
    ),
  ],
)

En app.dart, agrega lo siguiente después de _buildShrineTheme():

// TODO: Build a Shrine Text Theme (103)
TextTheme _buildShrineTextTheme(TextTheme base) {
  return base.copyWith(
    headline5: base.headline5!.copyWith(
      fontWeight: FontWeight.w500,
    ),
    headline6: base.headline6!.copyWith(
      fontSize: 18.0,
    ),
    caption: base.caption!.copyWith(
      fontWeight: FontWeight.w400,
      fontSize: 14.0,
    ),
    bodyText1: base.bodyText1!.copyWith(
      fontWeight: FontWeight.w500,
      fontSize: 16.0,
    ),
  ).apply(
    fontFamily: 'Rubik',
    displayColor: kShrineBrown900,
    bodyColor: kShrineBrown900,
  );
}

De esta manera, se toma un TextTheme y se cambia la apariencia de los encabezados, los títulos y las leyendas.

Si aplicas la fontFamily de esta forma, los cambios solo se aplicarán a los valores de escala de tipografía especificados en copyWith() (encabezado, título, leyenda).

Para algunas fuentes, configurarás un fontWeight personalizado en incrementos de 100: w500 (el peso 500) es, por lo general, el mediano y w400 es el normal.

Usa los nuevos temas de texto

Agrega los siguientes temas a _buildShrineTheme después del error:

// TODO: Add the text themes (103)
textTheme: _buildShrineTextTheme(base.textTheme),
textSelectionTheme: const TextSelectionThemeData(
  selectionColor: kShrinePink100,
),

Guarda el proyecto. Esta vez, también reinicia la app (acción conocida como Reinicio en caliente), ya que modificamos las fuentes.

Android

iOS

Página de la cuadrícula de productos de Shrine, con tema de textos

Página de la cuadrícula de productos de Shrine, con tema de textos

El texto se ve diferente en la pantalla principal y en la de acceso; parte del texto tiene la fuente Rubik y otra parte se renderiza en café, en lugar de blanco o negro. Los íconos también se muestran en café.

Reduce el texto

Las etiquetas son demasiado grandes.

En home.dart, cambia los children: de la columna más interna:

// TODO: Change innermost Column (103)
children: <Widget>[
// TODO: Handle overflowing labels (103)
  Text(
    product.name,
    style: theme.textTheme.button,
    softWrap: false,
    overflow: TextOverflow.ellipsis,
    maxLines: 1,
  ),
  const SizedBox(height: 4.0),
  Text(
    formatter.format(product.price),
    style: theme.textTheme.caption,
  ),
  // End new code
],

Centra y suelta el texto

Debes centrar las etiquetas y alinear el texto hacia la parte inferior de cada tarjeta, en lugar de la parte inferior de cada imagen.

Mueve las etiquetas hacia el final (parte inferior) del eje principal y modifícalas para centrarlas, como se muestra a continuación:

  // TODO: Align labels to the bottom and center (103)
  mainAxisAlignment: MainAxisAlignment.end,
  crossAxisAlignment: CrossAxisAlignment.center,

Guarda el proyecto.

Android

iOS

Página de la cuadrícula de productos de Shrine, con diferentes alineaciones de texto

Página de la cuadrícula de productos de Shrine, con diferentes alineaciones de texto

Así se ve mucho mejor.

Crea un tema de los campos de texto

Puedes crear un tema para la decoración de los campos de texto con un InputDecorationTheme.

En app.dart, en el método _buildShrineTheme(), especifica un valor inputDecorationTheme:.

// TODO: Decorate the inputs (103)
inputDecorationTheme: const InputDecorationTheme(
  border: OutlineInputBorder(),
),

Actualmente, los campos de texto tienen una decoración filled. Quita filled y especifica el inputDecorationTheme para darle el estilo de contorno a los campos de texto.

En login.dart, quita los valores filled: true, como se muestra a continuación:

// Remove filled: true values (103)
TextField(
  controller: _usernameController,
  decoration: const InputDecoration(
    // Removed filled: true
    labelText: 'Username',
  ),
),
const SizedBox(height: 12.0),
TextField(
  controller: _passwordController,
  decoration: const InputDecoration(
    // Removed filled: true
    labelText: 'Password',
  ),
  obscureText: true,
),

Reinicio en caliente: La pantalla de acceso debería tener la siguiente apariencia cuando se activa el campo de nombre de usuario (mientras escribes en él):

Android

iOS

Página de acceso a Shrine con enfoque en el campo de nombre de usuario

Página de acceso a Shrine con enfoque en el campo de nombre de usuario

Cuando escribes en el campo de texto, los márgenes y la etiqueta flotante se renderizan en el color principal. Sin embargo, no se ve con claridad. No es accesible para las personas que tienen dificultades al momento de distinguir los píxeles cuando el contraste de color no es lo suficientemente alto (para obtener más información, consulta "Colores accesibles" en el artículo de colores de los lineamientos de Material).

En app.dart, especifica un elemento focusedBorder: en inputDecorationTheme:.

// TODO: Decorate the inputs (103)
inputDecorationTheme: const InputDecorationTheme(
  border: OutlineInputBorder(),
  focusedBorder: OutlineInputBorder(
    borderSide: BorderSide(
      width: 2.0,
      color: kShrineBrown900,
    ),
  ),
),

Luego, especifica un floatingLabelStyle: en inputDecorationTheme: :

// TODO: Decorate the inputs (103)
inputDecorationTheme: const InputDecorationTheme(
  border: OutlineInputBorder(),
  focusedBorder: OutlineInputBorder(
    borderSide: BorderSide(
      width: 2.0,
      color: kShrineBrown900,
    ),
  ),
  floatingLabelStyle: TextStyle(
    color: kShrineBrown900,
  ),
),

Por último, haz que el botón Cancelar use el color secundario en lugar del primario para aumentar el contraste.

TextButton(
  child: const Text('CANCEL'),
  onPressed: () {
    _usernameController.clear();
    _passwordController.clear();
  },
  style: TextButton.styleFrom(
    primary: Theme.of(context).colorScheme.secondary,
  ),
),

Guarda el proyecto.

Android

iOS

Página de acceso a Shrine con el botón CANCELAR visible

Página de acceso a Shrine con el botón CANCELAR visible

6. Ajusta la elevación

Ahora que aplicaste un estilo en la página con colores y tipografía específicos que combinan con Shrine, ajusta la elevación.

Cambia la elevación del botón NEXT

La elevación predeterminada de los ElevatedButton es 2; y si quieres aumentarla, sigue estos pasos:

En login.dart, agrega un valor de style: al ElevatedButton NEXT:

ElevatedButton(
  child: const Text('NEXT'),
  onPressed: () {
    Navigator.pop(context);
  },
  style: ElevatedButton.styleFrom(
    elevation: 8.0,
  ),
),

Guarda el proyecto.

Ajusta la elevación de la tarjeta

Ahora, las tarjetas aparecen sobre una superficie blanca junto a la navegación del sitio.

En home.dart, agrega un valor de elevation: a las tarjetas.

// TODO: Adjust card heights (103)
elevation: 0.0,

Guarda el proyecto.

Android

iOS

Página de la cuadrícula de productos de Shrine, sin elevaciones para cada tarjeta

Página de la cuadrícula de productos de Shrine, sin elevaciones para cada tarjeta

Quitaste la sombra debajo de las tarjetas.

7. Agrega la forma

Shrine tiene un estilo geométrico atractivo y define los elementos con una forma octogonal o rectangular. Implementarás ese estilo de formas en las tarjetas de la pantalla principal, así como en los botones y los campos de texto de la pantalla de acceso.

Cambia las formas del campo de texto en la pantalla de acceso

En app.dart, importa el siguiente archivo:

import 'supplemental/cut_corners_border.dart';

En app.dart, modifica el tema de decoración de campos de texto para utilizar las esquinas cortadas:

// TODO: Decorate the inputs (103)
inputDecorationTheme: const InputDecorationTheme(
  border: CutCornersBorder(),
  focusedBorder: CutCornersBorder(
    borderSide: BorderSide(
      width: 2.0,
      color: kShrineBrown900,
    ),
  ),
  floatingLabelStyle: TextStyle(
    color: kShrineBrown900,
  ),
),

Cambia las formas de los botones en la pantalla de acceso

En login.dart, agrega un borde rectangular biselado al botón CANCEL, como se muestra a continuación:

TextButton(
  child: const Text('CANCEL'),
  onPressed: () {
    _usernameController.clear();
    _passwordController.clear();
  },
  style: TextButton.styleFrom(
    primary: Theme.of(context).colorScheme.secondary,
    shape: const BeveledRectangleBorder(
      borderRadius: BorderRadius.all(Radius.circular(7.0)),
    ),
  ),
),

Si TextButton no tiene una forma visible, ¿por qué agregarías una forma al borde? Se hace para que la animación de ondas esté vinculada a la misma forma cuando se toque.

Ahora, agrega la misma forma al botón NEXT, de la siguiente manera:

ElevatedButton(
  child: const Text('NEXT'),
  onPressed: () {
    Navigator.pop(context);
  },
  style: ElevatedButton.styleFrom(
    elevation: 8.0,
    shape: const BeveledRectangleBorder(
      borderRadius: BorderRadius.all(Radius.circular(7.0)),
    ),
  ),

Para cambiar la forma de todos los botones, también puedes usar el elevatedButtonTheme o textButtonTheme en app.dart. Esto se propone como un desafío para el estudiante.

Haz un reinicio en caliente.

Android

iOS

Página de acceso a Shrine con el tema de forma

Página de acceso a Shrine con el tema de forma

8. Cambia el diseño

A continuación, cambiarás el diseño para mostrar las tarjetas en diferentes tamaños y relaciones de aspecto, para que cada tarjeta tenga una apariencia única.

Reemplaza GridView por AsymmetricView

Ya escribiste los archivos para un diseño asimétrico.

En home.dart, agrega el siguiente trabajo de importación:

import 'supplemental/asymmetric_view.dart';

Borra _buildGridCards y reemplaza el body:

body: AsymmetricView(
  products: ProductsRepository.loadProducts(Category.all),
),

Guarda el proyecto.

Android

iOS

Página de productos de Shrine con un diseño asimétrico por el que es posible desplazarse horizontalmente

Página de productos de Shrine con un diseño asimétrico por el que es posible desplazarse horizontalmente

Ahora, se puede desplazar por los productos de forma horizontal en un patrón que se asemeja al de un tejido.

9. Prueba otro tema (opcional)

El color es una forma poderosa de expresar tu marca; un pequeño cambio de color puede generar un gran efecto en la experiencia del usuario. Para probarlo, observarás cómo se ve Shrine si el esquema de colores de la marca fuera ligeramente diferente.

Modifica los colores

En colors.dart, agrega el siguiente color:

const kShrinePurple = Color(0xFF5D1049);

En app.dart, cambia la función _buildShrineTheme() a la siguiente:

ThemeData _buildShrineTheme() {
  final ThemeData base = ThemeData.light();
  return base.copyWith(
    colorScheme: base.colorScheme.copyWith(
      primary: kShrinePurple,
      secondary: kShrinePurple,
      error: kShrineErrorRed,
    ),
    scaffoldBackgroundColor: kShrineSurfaceWhite,
    textSelectionTheme: const TextSelectionThemeData(
      selectionColor: kShrinePurple,
    ),
    inputDecorationTheme: const InputDecorationTheme(
      border: CutCornersBorder(),
      focusedBorder: CutCornersBorder(
        borderSide: BorderSide(
          width: 2.0,
          color: kShrinePurple,
        ),
      ),
      floatingLabelStyle: TextStyle(
        color: kShrinePurple,
      ),
    ),
  );
}

Haz un reinicio en caliente. Ahora, debería aparecer el tema nuevo.

Android

iOS

Página de acceso a Shrine con un tema púrpura y blanco

Página de acceso a Shrine con un tema púrpura y blanco

Android

iOS

Página de productos de Shrine con un tema púrpura y blanco

Página de productos de Shrine con un tema púrpura y blanco

El resultado es muy distinto. Cambia app.dart's _buildShrineTheme al que tenía antes de realizar este paso. Como alternativa, puedes descargar el código de partida para 104.

10. ¡Felicitaciones!

Ya creaste una app que se asemeja a las especificaciones de diseño que proporcionó el diseñador.

Próximos pasos

Ya utilizaste estos componentes de MDC: tema, tipografía, elevación y forma. En la biblioteca de MDC-Flutter, puedes explorar más componentes y subsistemas.

Examina los archivos del directorio supplemental para descubrir cómo creamos la cuadrícula de diseño asimétrico en la que se puede desplazar de forma horizontal.

¿Qué ocurre si el diseño que planificaste para tu app contiene elementos que no tienen componentes en la biblioteca de MDC? En MDC-104: Componentes avanzados de Material Design, se muestra cómo crear componentes personalizados con la biblioteca de MDC a fin de obtener un aspecto específico.

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