MDC-102: Structure et mise en page Material

1. Introduction

logo_components_color_2x_web_96dp.png

Material Components (MDC) aide les développeurs à implémenter Material Design. Conçu par une équipe d'ingénieurs et de spécialistes de l'expérience utilisateur chez Google, MDC propose des dizaines de composants d'interface utilisateur élégants et fonctionnels. Il est disponible pour Android, iOS, le Web et Flutter.material.io/develop.

Dans l'atelier de programmation MDC-101, vous avez utilisé deux éléments Material Components pour créer une page de connexion : des champs de texte et des boutons comportant des animations "tâche d'encre". Sur ces bases, enrichissons notre application en y ajoutant navigation, structure et données.

Objectifs de l'atelier

Dans cet atelier de programmation, vous allez créer un écran d'accueil pour Shrine, une application d'e-commerce pour la vente de vêtements et d'articles pour la maison. Cet écran contiendra :

  • Une barre d'application supérieure
  • Une liste de produits sous forme de grille

Android

iOS

Une application d'e-commerce avec une barre d'application supérieure et une grille pleine de produits

Une application d'e-commerce avec une barre d'application supérieure et une grille pleine de produits

Composants et sous-systèmes Material Flutter dans cet atelier de programmation

  • Barre d'application supérieure
  • Grilles
  • Fiches

Comment évalueriez-vous votre niveau d'expérience en développement avec Flutter ?

Débutant Intermédiaire Expert

2. Configurer l'environnement de développement Flutter

Pour cet atelier, vous avez besoin de deux logiciels : le SDK Flutter et un éditeur.

Vous pouvez exécuter l'atelier de programmation sur l'un des appareils suivants :

  • Un appareil Android ou iOS physique connecté à votre ordinateur et réglé en mode développeur.
  • Le simulateur iOS (outils Xcode à installer).
  • L'émulateur Android (qui doit être configuré dans Android Studio).
  • Un navigateur (Chrome est requis pour le débogage).
  • En tant qu'application de bureau Windows, Linux ou macOS. Vous devez développer votre application sur la plate-forme où vous comptez la déployer. Par exemple, si vous voulez développer une application de bureau Windows, vous devez le faire sous Windows pour accéder à la chaîne de compilation appropriée. Prenez également connaissance des exigences spécifiques aux systèmes d'exploitation, lesquelles sont détaillées sur docs.flutter.dev/desktop.

3. Télécharger l'appli de départ de l'atelier de programmation

Vous avez déjà suivi l'atelier MDC-101 ?

Si vous avez fini l'atelier de programmation MDC-101, votre code devrait être prêt pour commencer cet atelier. Passez à l'étape suivante : Ajouter une barre d'application supérieure.

Vous partez de zéro ?

Télécharger l'application de départ de l'atelier de programmation

L'application de départ se trouve dans le répertoire material-components-flutter-codelabs-102-starter_and_101-complete/mdc_100_series.

… ou cloner l'atelier depuis GitHub

Pour cloner cet atelier de programmation à partir de GitHub, exécutez les commandes suivantes :

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

Ouvrir le projet et exécuter l'application

  1. Ouvrez le projet dans l'éditeur de votre choix.
  2. Suivez les instructions concernant l'éditeur que vous avez choisi. Elles sont accessibles au paragraphe "Run the app" (Exécuter l'application) sur la page Get Started > Test drive (Premiers pas > Faire un essai).

Opération réussie. La page de connexion de Sanctuaire venant de l'atelier de programmation MDC-101 doit s'afficher sur votre appareil.

Android

iOS

Page de connexion comportant les champs "Nom d'utilisateur" et "Mot de passe", et les boutons "Annuler" et "Suivant"

Page de connexion comportant les champs "Nom d'utilisateur" et "Mot de passe", et les boutons "Annuler" et "Suivant"

Maintenant que l'écran de connexion est en place, ajoutons quelques produits dans l'application.

4. Ajouter une barre d'application supérieure

Pour le moment, si vous cliquez sur le bouton "Next", l'écran d'accueil s'affiche avec le message "You did it!" (Vous avez réussi.). Parfait. Cependant, notre utilisateur n'a rien à faire et ne sait pas où il se trouve dans l'application. Pour vous aider, il est temps d'ajouter la navigation.

Material Design propose des formats de navigation qui garantissent une grande facilité d'utilisation. L'un des composants les plus visibles est la barre d'application supérieure.

Pour offrir aux utilisateurs un accès rapide à d'autres actions, ajoutons une barre d'application supérieure.

Ajouter un widget AppBar

Dans home.dart, ajoutez une AppBar à "Scaffold", puis supprimez le const mis en surbrillance:

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

L'ajout de l'AppBar au champ appBar: de l'échafaudage nous permet d'obtenir une mise en page parfaite et sans frais, en laissant la barre d'application en haut de la page et en dessous du corps.

Enregistrez le projet. Une fois l'application Shrine mise à jour, cliquez sur Next (Suivant) pour afficher l'écran d'accueil.

Android

iOS

écran indiquant "Vous avez réussi !"

écran indiquant "Vous avez réussi !"

L'élément est bien positionné, mais il lui manque un titre.

Ajouter un widget Text

Dans home.dart, ajoutez un titre à la barre d'application AppBar :

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

Enregistrez le projet.

Android

iOS

une barre d'application dont le titre est Sanctuaire

une barre d'application dont le titre est Sanctuaire

De nombreuses barres d'application comportent un bouton à côté du titre. Ajoutons une icône de menu dans notre application.

Ajouter un bouton IconButton en début de ligne

Toujours dans home.dart, définissez un élément IconButton pour le champ leading: de la barre d'application AppBar. (Placez-le avant le champ title: conformément à la disposition du début à la fin de la ligne) :

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

Enregistrez le projet.

Android

iOS

une barre d'application avec un titre et une icône de menu hamburger

une barre d'application avec un titre et une icône de menu hamburger

L'icône de menu (également appelée "hamburger"), s'affiche à l'emplacement souhaité.

Vous pouvez également ajouter des boutons en fin de ligne. Dans Flutter, ces boutons sont appelés "actions".

Ajouter des actions

Il reste assez de place pour deux autres éléments IconButton.

Ajoutez-les à l'instance AppBar après le titre :

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

Enregistrez le projet. Votre écran d'accueil devrait se présenter ainsi :

Android

iOS

une barre d'application avec Sanctuaire comme titre et icône de menu hamburger, et icônes de recherche et de personnalisation à la fin

une barre d'application avec Sanctuaire comme titre et icône de menu hamburger, et icônes de recherche et de personnalisation à la fin

L'application dispose désormais d'un bouton en début de ligne, d'un titre et de deux actions à droite. La barre d'application présente également un effet d'élévation dû à un léger ombrage indiquant qu'elle se trouve à un niveau différent de celui du contenu.

5. Ajouter une fiche dans une grille

Maintenant que notre application est un peu plus structurée, organisons ses contenus en les plaçant dans des fiches.

Ajouter un widget GridView

Commençons par ajouter une fiche sous la barre d'application supérieure. À l'heure actuelle, le widget Card (Fiche) ne contient pas suffisamment d'informations pour se positionner de façon visible. Nous allons donc l'encapsuler dans un widget GridView.

Remplacez le centre dans le corps de l'élément Scaffold par un widget 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()],
),

Analysons ce code. Le widget Griview appelle le constructeur count(), car le nombre d'éléments qu'il affiche peut être compté et n'est pas infini. Mais il a besoin d'informations supplémentaires pour définir sa mise en page.

La variable crossAxisCount: indique le nombre d'éléments par ligne. Nous voulons obtenir deux colonnes.

Le champ padding: définit un espace libre sur les quatre côtés du widget GridView. Bien entendu, cette marge n'est pas visible à la suite de l'élément ou sous celui-ci, car aucun enfant GridView n'a encore été placé à côté de cet élément pour le moment.

Le champ childAspectRatio: identifie la taille des éléments sous forme de proportions (largeur sur hauteur).

Par défaut, GridView crée des blocs de taille identique.

Nous avons une fiche, mais elle est vide. Ajoutons des widgets enfants à cette fiche.

Mettre en page les contenus

Les fiches doivent comporter des zones pour une image, un titre et du texte secondaire.

Mettez à jour les enfants du widget 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'),
            ],
          ),
        ),
      ],
    ),
  )
],

Ce code ajoute un widget Column (Colonne) qui permet de disposer les widgets enfants verticalement.

Le paramètre crossAxisAlignment: field spécifie la valeur CrossAxisAlignment.start, ce qui signifie "aligner le texte sur le côté 'début de ligne'".

Le widget AspectRatio définit les proportions de l'image affichée, quel que soit le type d'image fourni.

L'élément Padding définit une marge pour le texte.

Les deux widgets Text sont placés l'un au-dessus de l'autre et séparés par huit points d'espace vide (SizedBox). Nous créons un autre élément Column pour leur donner des marges intérieures.

Enregistrez le projet.

Android

iOS

Un seul élément avec une image, un titre et un texte secondaire

Un seul élément avec une image, un titre et un texte secondaire

Dans cet aperçu, vous pouvez voir que la fiche est positionnée avec une marge, ses coins sont arrondis et elle projette une ombre (représentant son élévation). La forme entière est appelée "conteneur" dans le système Material Design (à ne pas confondre avec la classe de widget appelée Container).

Les fiches s'affichent généralement dans une collection avec d'autres fiches. Disposons-les sous forme de collection dans une grille.

6. Créer une collection de fiches

Lorsque plusieurs fiches sont présentes sur un écran, elles sont regroupées dans une ou plusieurs collections. Les fiches d'une collection sont coplanaires : elles ont toute la même élévation au repos (c'est-à-dire lorsqu'elles ne sont pas sélectionnées ou déplacées, ce que nous ne ferons pas ici).

Créer une collection de fiches

Pour l'instant, notre fiche se positionne à l'intérieur du champ children: de la grille GridView. Cela entraîne une grande quantité de code imbriqué qui peut être difficile à lire. Transformons cette partie du code en une fonction permettant de générer autant de fiches vides que nécessaire et de renvoyer une liste de fiches.

Créez une fonction privée au-dessus de la fonction build() (n'oubliez pas que les fonctions commençant par un trait de soulignement sont des API privées) :

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

Affectez les fiches générées au champ children du widget GridView. N'oubliez pas de remplacer tous les éléments contenus dans le widget GridView par ce nouveau code :

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

Enregistrez le projet.

Android

iOS

une grille d'éléments avec une image, un titre et un texte secondaire

une grille d'éléments avec une image, un titre et un texte secondaire

Les cartes sont présentes, mais elles n'affichent rien pour le moment. Le moment est venu d'ajouter des données produit.

Ajouter des données produit

L'application contient quelques produits avec des images, des noms et des prix. Ajoutons cela aux widgets déjà présents dans la fiche

Ensuite, dans home.dart, importez un nouveau package et quelques fichiers fournis pour un modèle de données :

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

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

Enfin, modifiez _buildGridCards() pour récupérer les informations sur le produit et utiliser ces données dans les fiches :

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

REMARQUE : Attendez avant de lancer la compilation et l'exécution. Il reste une modification à apporter.

Modifiez également la fonction build() pour transmettre la valeur BuildContext à _buildGridCards() avant de lancer la compilation :

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

Redémarrez l'application à chaud.

Android

iOS

une grille d'éléments avec une image, un titre et un prix

une grille d'éléments avec une image, un titre et un prix

Vous remarquerez peut-être que nous n'ajoutons pas d'espace vertical entre les fiches. car ils ont par défaut quatre marges en haut et en bas.

Enregistrez le projet.

Les données produit s'affichent, mais les images sont entourées d'un espace inutile. Par défaut, les images sont dessinées avec le champ BoxFit défini sur .scaleDown (dans ce cas). Remplacez cette valeur par .fitWidth pour qu'elle effectue un zoom avant et supprime l'espace inutile.

Ajoutez un champ fit: à l'image avec la valeur BoxFit.fitWidth :

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

Android

iOS

une grille d'articles avec une image recadrée, le titre du produit et le prix

une grille d'articles avec une image recadrée, le titre du produit et le prix

Nos produits s'affichent désormais parfaitement dans l'application.

7. Félicitations !

Notre application propose un fonctionnement de base permettant à l'utilisateur de passer de l'écran de connexion à un écran d'accueil où les produits sont affichés. Quelques lignes de code nous ont suffi pour ajouter une barre d'application supérieure (avec un titre et trois boutons) et des fiches (pour présenter le contenu de notre application). L'écran d'accueil obtenu est simple et fonctionnel, et présente une structure de base et des contenus exploitables.

Étapes suivantes

Avec la barre d'application, la fiche, le champ de texte et le bouton situés en haut de la page, nous avons maintenant utilisé quatre composants principaux de la bibliothèque Material Flutter. Pour en savoir plus, consultez le catalogue de composants Material.

Même si notre application fonctionne parfaitement, elle ne reflète pas une identité de marque ni un point de vue particulier. Dans l'atelier MDC-103 : Utilisation des thèmes de Material Design (couleur, formes, élévation et type), nous allons personnaliser le style de ces composants pour exprimer une marque moderne et dynamique.

La réalisation de cet atelier de programmation m'a demandé un temps et des efforts raisonnables

Tout à fait d'accord D'accord Ni d'accord, ni pas d'accord Pas d'accord Pas du tout d'accord

Je souhaite continuer à utiliser Material Components

Tout à fait d'accord D'accord Ni d'accord, ni pas d'accord Pas d'accord Pas du tout d'accord