MDC-102 no Flutter: estrutura e layout do Material Design

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 oferecem dezenas de componentes de IU bonitos e funcionais disponíveis para Android, iOS, Web e Flutter.material.io/develop

No codelab MDC-101, você usou dois componentes do Material Design para criar uma página de login: campos de texto e botões de ondulações com tinta. Agora, vamos nos aprofundar nessa base adicionando navegação, estrutura e dados.

O que você criará

Neste codelab, você criará uma tela inicial para o app Shrine, um app de comércio eletrônico que vende roupas e artigos domésticos. Ele conterá:

  • Uma barra de apps superior.
  • Uma lista de grade cheia de produtos.

Android

iOS

App de e-commerce com uma barra de apps superior e uma grade de produtos

App de e-commerce com uma barra de apps superior e uma grade de produtos

Componentes e subsistemas do Flutter do Material Design neste codelab

  • Barra de apps superior
  • Grades.
  • Cards

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-101?

Se você concluiu o MDC-101, o código para este codelab já está pronto. Pular para a etapa: Adicionar uma barra de apps superior.

Está começando do zero?

Fazer o download do app inicial do codelab

O app inicial está localizado no diretório material-components-flutter-codelabs-102-starter_and_101-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 102-starter_and_101-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. Você verá a página de login do Shrine do codelab MDC-101 no seu dispositivo.

Android

iOS

página de login com campos de nome de usuário e senha, botões "cancelar" e "próximo"

página de login com campos de nome de usuário e senha, botões "cancelar" e "próximo"

Agora que a tela de login está pronta, vamos preencher o app com alguns produtos.

4. Adicionar uma barra de apps superior

Agora, se você clicar no botão "Next", poderá ver a tela inicial com a mensagem "You did it!". Isso é ótimo. Mas agora nosso usuário não tem mais o que fazer ou sabe onde ele está no app. Para ajudar, é hora de adicionar a navegação.

O Material Design oferece padrões de navegação que garantem uma boa usabilidade. Um dos componentes mais visíveis é a barra de apps superior.

Para que os usuários possam navegar e ter acesso rápido a outras ações, vamos adicionar uma barra de apps superior.

Adicionar um widget AppBar

Em home.dart, adicione uma AppBar ao Scaffold e remova o const destacado:

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

Adicionar a AppBar ao campo appBar: do Scaffold oferece um layout perfeito e sem custo financeiro, mantendo a AppBar na parte superior da página e o corpo embaixo.

Salve o projeto. Quando o app Shrine for atualizado, clique em Next para ver a tela inicial.

Android

iOS

tela dizendo "você conseguiu!"

tela dizendo "você conseguiu!"

O AppBar está ótimo, mas precisa de um título.

Adicionar um widget Text

Em home.dart, adicione um título ao AppBar:

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

Salve o projeto.

Android

iOS

uma barra de apps com o Shrine como título

uma barra de apps com o Shrine como título

Muitas barras de apps têm um botão ao lado do título. Vamos adicionar um ícone de menu ao app.

Adicionar um IconButton inicial

Ainda no home.dart, defina um IconButton para o campo leading: do AppBar. Coloque-o antes do campo title: para copiar a ordem do início ao fim:

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

Salve o projeto.

Android

iOS

uma barra de apps com o Shrine como título e um ícone de menu de navegação

uma barra de apps com o Shrine como título e um ícone de menu de navegação

O ícone de menu (também conhecido como "hamburguer") será exibido exatamente onde você esperava.

Também é possível adicionar botões ao lado final do título. No Flutter, elas são chamadas de "ações".

Adicionar ações

Há espaço para adicionar mais dois IconButtons.

Adicione-os à instância do AppBar após o 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');
    },
  ),
],

Salve o projeto. A tela inicial ficará assim:

Android

iOS

uma barra de apps com o Shrine como título e um ícone de menu de navegação, além de ícones de pesquisa e personalização à direita

uma barra de apps com o Shrine como título e um ícone de menu de navegação, além de ícones de pesquisa e personalização à direita

Agora, o app tem um botão inicial, um título e duas ações no lado direito. A barra do app também tem uma elevação representada por uma sombra sutil que mostra que a barra está em uma camada diferente da do conteúdo.

5. Adicionar um card a uma grade

Agora que nosso app tem uma estrutura básica, vamos organizar o conteúdo colocando-o em cards.

Adicionar um GridView

Para começar, vamos adicionar um card abaixo da barra de apps superior. Por enquanto, o widget Card sozinho não tem informações suficientes para ser exibido. Por isso, queremos encapsulá-lo em um widget GridView.

Substitua o centro do corpo do Scaffold por um 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 descompactar esse código. O GridView invocará o construtor count(), já que o número de itens exibidos é contável, ou seja, não é infinito. No entanto, ele precisa de mais informações para definir o layout.

O crossAxisCount: especifica quantos itens serão exibidos. Queremos exibir duas colunas.

O campo padding: fornece espaço para os quatro lados do GridView. Não é possível ver o preenchimento dos lados finais ou inferiores porque ainda não há filhos do GridView ao lado deles.

O campo childAspectRatio: identifica o tamanho dos itens com base na proporção (largura dividida pela altura).

Por padrão, o GridView cria blocos com tamanhos iguais.

Agora, temos um card, mas ele está vazio. Vamos adicionar widgets filhos ao nosso card.

Criar o layout com o conteúdo

Os cards precisam ter regiões para uma imagem, um título e um texto secundário.

Atualize os filhos do 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'),
            ],
          ),
        ),
      ],
    ),
  )
],

Esse código adiciona um widget Column usado para exibir os widgets filhos verticalmente em uma coluna.

O crossAxisAlignment: field especifica CrossAxisAlignment.start. Isso significa "alinhar o texto com a borda inicial".

O widget AspectRatio define o formato da imagem, independentemente do tipo dela.

O Padding afasta levemente o texto da lateral.

Os dois widgets Text são dispostos verticalmente com 8 pontos de espaço vazio entre eles (SizedBox). Usaremos o widget Column para criar outra coluna que armazenará os widgets Text no Padding.

Salve o projeto.

Android

iOS

um único item com imagem, título e texto secundário

um único item com imagem, título e texto secundário

Nesta prévia, é possível ver que o card se enquadra na borda, com cantos arredondados e uma sombra (que representa a elevação dele). No Material Design, a forma como um todo é conhecida como um "contêiner". Não confunda essa forma com a classe de widget Container.

Os cards geralmente são exibidos com outros em uma coleção. Vamos exibi-los como uma coleção em uma grade.

6. Criar uma coleção de cards

Sempre que vários cards forem exibidos em uma tela, eles serão agrupados em uma ou mais coleções. Os cards de uma coleção são coplanares, ou seja, compartilham a mesma elevação de repouso (a menos que os cards sejam retirados ou arrastados, mas não faremos isso neste codelab).

Multiplicar o card em uma coleção

Agora, nosso card é construído in-line no campo children: do GridView. Ele tem um código aninhado grande que pode ser difícil de ler. Vamos extraí-lo para uma função que pode gerar quantos cards vazios quisermos e retornar uma lista de cards.

Crie uma nova função particular acima da função build() (as funções que começam com um sublinhado são APIs particulares):

// 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;
}

Atribua os cards gerados ao campo children do GridView. Lembre-se de substituir todo o conteúdo do GridView por esse novo 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(10) // Replace
),

Salve o projeto.

Android

iOS

uma grade de itens com uma imagem, um título e um texto secundário

uma grade de itens com uma imagem, um título e um texto secundário

Os cards estão lá, mas ainda não mostram nada. Agora é hora de adicionar os dados do produto.

Adicionar dados de produtos

O app tem alguns produtos com imagens, nomes e preços. Vamos adicioná-lo aos widgets que já criamos no card.

Em seguida, em home.dart, importe um novo pacote e alguns arquivos que fornecemos para um modelo de dados:

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

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

Por fim, mude o método _buildGridCards() para buscar as informações do produto e use esses dados nos cards:

// 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();
}

OBSERVAÇÃO: o app ainda não poderá ser compilado nem executado. Temos que fazer mais uma mudança.

Além disso, altere a função build() para transmitir o BuildContext para o método _buildGridCards() antes de tentar compilar o 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
),

Reinicie o app.

Android

iOS

uma grade de itens com uma imagem, título do produto e preço

uma grade de itens com uma imagem, título do produto e preço

Talvez você tenha percebido que não adicionamos nenhum espaço vertical entre os cards. Isso ocorre porque, por padrão, eles têm quatro pontos de margem na parte superior e inferior.

Salve o projeto.

Os dados do produto são exibidos, mas as imagens têm espaço extra ao redor delas. Por padrão, as imagens são exibidas pelo widget BoxFit de .scaleDown (nesse caso). Vamos alterar isso para usar .fitWidth para aumentar o zoom e remover o espaço em branco extra das imagens.

Adicione um campo fit: à imagem com um valor de BoxFit.fitWidth:

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

Android

iOS

uma grade de itens com uma imagem cortada, o título do produto e o preço;

uma grade de itens com uma imagem cortada, o título do produto e o preço;

Agora, nossos produtos estão sendo exibido perfeitamente no app!

7. Parabéns!

Nosso app tem um fluxo básico que leva o usuário da tela de login a uma tela inicial, onde os produtos são exibidos. Em apenas algumas linhas de código, adicionamos uma barra de apps superior (com um título e três botões) e cards (para exibir o conteúdo do nosso app). Nossa tela inicial agora é simples e funcional, com uma estrutura básica e um conteúdo acionável.

Próximas etapas

Agora, com a barra de apps, o card, o campo de texto e o botão, usamos quatro componentes principais da biblioteca do Flutter do Material Design. Você pode explorar mais acessando o catálogo de widgets de componentes do Material Design (em inglês).

Embora nosso app funcione perfeitamente, ele ainda não representa uma marca ou ponto de vista específicos. No codelab MDC-103: temas do Material Design usando cor, forma, elevação e tipo, vamos personalizar o estilo desses componentes para representar uma marca vibrante e moderna.

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