MDC-103 Flutter: temas do Material Design com cores, formas, elevação e tipo

1. Introdução

logo_components_color_2x_web_96dp.png

Os componentes do Material Design (MDC, na sigla em inglês) ajudam os desenvolvedores a implementar o Material Design. Criados por uma equipe de engenheiros e designers de UX do Google, os MDC apresentam dezenas de componentes de IU bonitos e funcionais e estão disponíveis para Android, iOS, Web e Flutter.material.io/develop

Agora, você pode usar os MDC para personalizar o estilo distinto dos seus apps mais do que nunca. A recente expansão do Material Design oferece aos designers e desenvolvedores uma maior flexibilidade para expressar a marca do produto.

Nos codelabs MDC-101 e MDC-102, você usou componentes do Material Design (MDC) para criar os elementos básicos do Shrine, um app de e-commerce que vende roupas e artigos domésticos. Esse app contém um fluxo de usuários que começa com uma tela de login e direciona o usuário para uma tela inicial que exibe produtos.

O que você criará

Neste codelab, você personalizará o app Shrine usando:

  • Cor
  • Tipografia
  • Elevação
  • Forma
  • Layout

Android

iOS

Página de login do Shrine com tema marrom e rosa

Página de login do Shrine com tema marrom e rosa

Página de produto do Shrine com uma barra de apps superior e uma grade assimétrica com rolagem horizontal cheia de produtos e tema rosa e marrom

Página de produto do Shrine com uma barra de apps superior e uma grade assimétrica com rolagem horizontal cheia de produtos e tema rosa e marrom

Componentes e subsistemas do MDC-Flutter neste codelab

  • Temas
  • Tipografia
  • Elevação
  • Lista de imagens

Como você classificaria seu nível de experiência em desenvolvimento com o Flutter?

Iniciante Intermediário Proficiente

2. Configurar o ambiente de desenvolvimento do Flutter

Você precisa de dois softwares para concluir este laboratório: o SDK do Flutter e um editor.

É possível executar o codelab usando qualquer um destes dispositivos:

  • Um dispositivo físico Android ou iOS conectado ao seu computador e configurado para o modo de desenvolvedor.
  • O simulador para iOS, que exige a instalação de ferramentas do Xcode.
  • O Android Emulator, que requer configuração no Android Studio.
  • Um navegador (o Chrome é necessário para depuração).
  • Como um aplicativo para computador Windows, Linux ou macOS. Você precisa desenvolver na plataforma em que planeja implantar. Portanto, se quiser desenvolver um app para um computador Windows, você terá que desenvolver no Windows para acessar a cadeia de builds adequada. Há requisitos específicos de cada sistema operacional que são abordados em detalhes em docs.flutter.dev/desktop.

3. Fazer o download do app inicial do codelab

Está continuando do MDC-102?

Se você concluiu o MDC-102, o código para este codelab já está pronto. Pule para a etapa: Mudar as cores.

Está começando do zero?

Faça o download do app inicial do codelab

O app inicial está localizado no diretório material-components-flutter-codelabs-103-starter_and_102-complete/mdc_100_series.

... ou clone-o do GitHub

Para clonar este codelab do GitHub, execute estes 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

Abrir o projeto e executar o app

  1. Abrir o projeto no seu editor favorito.
  2. Siga as instruções para "Executar o app" em Get Started: Test drive no editor escolhido.

Pronto. A página de login do Shrine dos codelabs anteriores será exibida no seu dispositivo.

Android

iOS

página de login sem tema do Shrine

página de login sem tema do Shrine

Clique em "Next" para ver a página inicial do codelab anterior.

Android

iOS

página de grade de produtos do Shrine sem tema

página de grade de produtos do Shrine sem tema

4. Mudar as cores

Um esquema de cores que representa a marca do Shrine foi criado, e o designer quer que você implemente esse esquema no app.

Para começar, vamos importar essas cores para o nosso projeto.

Criar colors.dart

Crie um novo arquivo dart em lib com o nome colors.dart. Importe os componentes do Material Design e adicione constantes de valores de cor:

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 cores personalizada

Este tema de cor foi criado por um designer usando cores personalizadas, mostradas na imagem abaixo. Ele contém cores que foram selecionadas da marca do Shrine e aplicadas ao Material Theme Editor, que as usou como base para criar uma paleta mais completa. Essas cores não são das Paleta de cores do Material Design de 2014 (link em inglês).

O Material Theme Editor organizou as cores em tonalidades marcadas numericamente, incluindo as etiquetas 50, 100, 200 e assim por diante até chegar em 900 para cada cor. O Shrine usa apenas as tonalidades 50, 100 e 300 da amostra rosa e a 900 da amostra marrom.

b9170eb94fd3b106.jpeg f8b4b97f898f154e.png

Cada parâmetro colorido de um widget é associado a uma cor desses esquemas. Por exemplo, a cor das decorações de um campo de texto que está recebendo ativamente a entrada do usuário precisa ser a cor principal do tema. Se essa cor não for acessível (fácil de ver em relação ao segundo plano), use a classe PrimaryVariant.

Essas variações foram criadas para as diretrizes do Material Design de 2014 e ainda estão disponíveis nas diretrizes atuais (Artigo do sistema de cores, em inglês) e no MDC-Flutter. Para acessá-las no código, basta chamar a cor de base e depois a tonalidade (geralmente, um valor na casa das centenas). Por exemplo, a cor Pink 400 é acessada com o comando: Colors.pink[400].

Não há problema em usar essas paletas para seus designs e códigos. Caso já tenha cores específicas para sua marca, você poderá gerar paletas harmoniosas usando a ferramenta de geração de paleta ou o Material Theme Editor.

Agora que já temos as cores que queremos usar, podemos aplicá-las à IU. Faremos isso definindo os valores de um widget ThemeData que será aplicado à instância de MaterialApp na parte superior da nossa hierarquia de widgets.

Personalizar ThemeData.light()

O Flutter inclui alguns temas integrados. O tema claro é um deles. Em vez de criar um widget ThemeData do zero, copiaremos o tema claro e mudaremos os valores para personalizá-lo em nosso app.

Vamos importar colors.dart em app.dart.

import 'colors.dart';

Em seguida, adicione o código a seguir ao app.dart fora do escopo da classe 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)
  );
}

Agora, defina o theme: no final da função build() do ShrineApp (no widget MaterialApp) para ser nosso novo tema:

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

Salve o projeto. Sua tela de login ficará assim:

Android

iOS

página de login do Shrine com tema rosa e marrom

página de login do Shrine com tema rosa e marrom

E sua tela inicial vai ficar assim:

Android

iOS

página de grade de produtos do Shrine com tema rosa e marrom

página de grade de produtos do Shrine com tema rosa e marrom

5. Modificar a tipografia e os estilos de etiquetas

Além das mudanças das cores, o designer também forneceu uma tipografia específica para ser usada. O ThemeData do Flutter inclui três temas de texto. Cada um deles é uma coleção de estilos de texto, como "manchete" e "título". Usaremos alguns estilos em nosso app e mudaremos alguns dos valores.

Personalizar o tema do texto

Para importar fontes para o projeto, elas precisam ser adicionadas ao arquivo pubspec.yaml.

No arquivo pubspec.yaml, adicione o código a seguir imediatamente após a tag flutter::

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

Agora você pode acessar e usar a fonte Rubik.

Como solucionar problemas no arquivo pubspec

Poderá haver erros na execução de pub get se você recortar e colar a declaração acima. Se houver erros, comece removendo o espaço em branco no começo da declaração e substitua-o por espaços usando o recuo de dois espaços. Use dois espaços antes de:

fonts:

Quatro espaços antes de:

family: Rubik

E assim por diante.

Se a mensagem Mapping values are not allowed here for exibida, verifique o recuo da linha que causou o problema e o recuo das linhas acima dela.

Em login.dart, mude o código a seguir em Column():

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

Em app.dart, adicione o código a seguir após _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,
  );
}

Esse código usa um TextTheme e muda a forma como as manchetes, os títulos e as legendas são exibidos.

Usar a fontFamily dessa maneira faz que as mudanças sejam aplicadas somente aos valores de escala de tipografia especificados em copyWith() (manchete, título, legenda).

Para algumas fontes, definimos um fontWeight personalizado em incrementos de 100: w500 (o peso 500) corresponde a médio e w400 a regular.

Usar os novos temas de textos

Adicione os estes temas ao _buildShrineTheme após o erro:

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

Salve o projeto. Desta vez, reinicie também o app (ou faça uma recarga dinâmica) porque modificamos as fontes.

Android

iOS

Página da grade do produto do Shrine com temas de texto aplicados

Página da grade do produto do Shrine com temas de texto aplicados

O texto na tela inicial e de login parece diferente. Alguns textos usam a fonte Rubik, e outros são renderizados em marrom, em vez de preto ou branco. Os ícones também são renderizados em marrom.

Reduzir o texto

As etiquetas estão muito grandes.

Em home.dart, mude children: da coluna mais 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
],

Centralizar e abaixar o texto

Queremos centralizar as etiquetas e alinhar o texto com a parte inferior de cada card, em vez da parte inferior de cada imagem.

Mova as etiquetas para o final (abaixo) do eixo principal e centralize-as:

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

Salve o projeto.

Android

iOS

Página da grade do produto do Shrine com alinhamento de texto diferente

Página da grade do produto do Shrine com alinhamento de texto diferente

Ficou muito melhor.

Atribuir um tema aos campos de texto

Também é possível aplicar um tema à decoração dos campos de texto usando um InputDecorationTheme.

Em app.dart, no método _buildShrineTheme(), especifique um valor de inputDecorationTheme::

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

No momento, os campos de texto têm uma decoração filled. Vamos remover isso. Se você remover filled e especificar inputDecorationTheme, os campos de texto usarão o estilo delineado.

Em login.dart, remova os valores filled: true:

// 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,
),

Execute uma recarga dinâmica. A tela de login ficará como no exemplo a seguir quando o campo "Username" estiver ativo (quando o usuário estiver digitando nele):

Android

iOS

Página de login do Shrine com foco no campo de nome de usuário

Página de login do Shrine com foco no campo de nome de usuário

Digite em um campo de texto: as bordas e os rótulos flutuantes são renderizados na cor primária. Mas eles não são fáceis de ver. O app não é acessível para pessoas com problemas para diferenciar pixels sem um contraste de cor alto o suficiente. Para ver mais informações sobre cores acessíveis, consulte a seção "Accessible colors" no artigo sobre as cores (link em inglês) do Material Design.

Em app.dart, especifique uma focusedBorder: no inputDecorationTheme: :

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

Depois, especifique um floatingLabelStyle: em 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 fim, vamos fazer com que o botão "Cancelar" use a cor secundária em vez da primária para aumentar o contraste.

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

Salve o projeto.

Android

iOS

Página de login do Shrine com o botão "CANCELAR" acessível

Página de login do Shrine com o botão "CANCELAR" acessível

6. Ajustar a elevação

Agora que você definiu o estilo da página com cores e tipografia específicas para combinar com o Shrine, vamos ajustar a elevação.

Mudar a elevação do botão NEXT

A elevação padrão para ElevatedButton é 2. Vamos aumentá-la.

Em login.dart, adicione um valor de style: ao ElevatedButton PRÓXIMA:

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

Salve o projeto.

Ajustar a elevação do card

No momento, os cards aparecem em uma superfície branca ao lado da navegação do site.

Em home.dart, adicione um valor de elevation: aos cards:

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

Salve o projeto.

Android

iOS

Página da grade do produto do Shrine sem elevação para cada cartão

Página da grade do produto do Shrine sem elevação para cada cartão

Você removeu a sombra embaixo dos cards.

7. Adicionar uma forma

O Shrine tem um estilo geométrico que define elementos usando formas octogonais ou retangulares. Vamos implementar esse estilo de forma nos cards na tela inicial e nos campos de texto e botões da tela de login.

Mudar as formas dos campos de texto na tela de login

Em app.dart, importe o seguinte arquivo:

import 'supplemental/cut_corners_border.dart';

Ainda em app.dart, modifique o tema de decoração do campo de texto para usar uma borda de cantos recortados:

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

Mudar as formas dos botões na tela de login

Em login.dart, adicione uma borda retangular chanfrada ao botão CANCEL:

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)),
    ),
  ),
),

O TextButton não tem uma forma visível. Então, por que estamos adicionando um ícone de borda a ele? Fazemos isso para que a animação de ondulação seja ligada à mesma forma quando for tocada.

Agora, adicione a mesma forma ao botão NEXT:

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 mudar a forma de todos os botões, também podemos usar elevatedButtonTheme ou textButtonTheme em app.dart. Você poderá fazer depois, como um desafio.

Faça uma recarga dinâmica.

Android

iOS

Página de login do Shrine com tema de forma aplicado

Página de login do Shrine com tema de forma aplicado

8. Mudar o layout

Agora, vamos modificar o layout para mostrar os cards em proporções e tamanhos distintos, de modo que cada card seja diferente dos demais.

Substituir uma GridView por uma AsymmetricView

Já criamos os arquivos para um layout assimétrico.

Em home.dart, adicione a seguinte importação:

import 'supplemental/asymmetric_view.dart';

Exclua _buildGridCards e substitua body:

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

Salve o projeto.

Android

iOS

Página de produto do Shrine com layout assimétrico de rolagem horizontal

Página de produto do Shrine com layout assimétrico de rolagem horizontal

Agora, os produtos rolam horizontalmente na tela em um padrão inspirado pela costura.

9. Testar outro tema (opcional)

As cores são uma maneira eficiente de expressar sua marca, e uma pequena mudança de cor pode ter um grande efeito na experiência do usuário. Para testar isso, vamos conferir como o Shrine seria se o esquema de cores da marca fosse um pouco diferente.

Modificar cores

Em colors.dart, adicione a seguinte cor:

const kShrinePurple = Color(0xFF5D1049);

Em app.dart, mude a função _buildShrineTheme() para o seguinte:

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,
      ),
    ),
  );
}

Faça uma recarga dinâmica. O novo tema será exibido.

Android

iOS

Página de login do Shrine com um tema roxo e branco

Página de login do Shrine com um tema roxo e branco

Android

iOS

Página de produto do Shrine com um tema roxo e branco

Página de produto do Shrine com um tema roxo e branco

O resultado é muito diferente. Vamos reverter app.dart's _buildShrineTheme ao que era antes desta etapa. Como alternativa, faça o download do código inicial para 104.

10. Parabéns!

Agora, você já criou um app que parece seguir as especificações do seu designer.

Próximas etapas

Até agora, você já usou os seguintes componentes do MDC: tema, tipografia, elevação e forma. Você pode ver mais componentes e subsistemas na biblioteca MDC-Flutter.

Analise os arquivos no diretório supplemental para ver como fizemos a rolagem horizontal e a grade de layout assimétrica.

E se o design do app planejado tiver elementos que não têm componentes na biblioteca MDC? No codelab MDC-104: componentes avançados do Material Design, mostraremos como criar componentes personalizados usando a biblioteca MDC para criar uma aparência específica para o app.

Este codelab exigiu esforço e tempo normais para ser concluído

Concordo totalmente Concordo Não concordo nem discordo Discordo Discordo totalmente

Quero continuar usando os componentes do Material Design no futuro

Concordo totalmente Concordo Não concordo nem discordo Discordo Discordo totalmente