MDC-102 Flutter: Materialstruktur und Layout

1. Einführung

logo_components_color_2x_web_96dp.png

Material Components (MDC) helfen Entwicklern bei der Implementierung von Material Design. MDC wurde von einem Team aus Entwicklern und UX-Designern bei Google entwickelt und bietet Dutzende von ansprechenden und funktionalen UI-Komponenten. Es ist für Android, iOS, das Web und Flutter verfügbar.material.io/develop

Im Codelab MDC-101 haben Sie zwei Material-Komponenten verwendet, um eine Anmeldeseite zu erstellen: Textfelder und Schaltflächen mit Tintenwellen. Jetzt wollen wir diese Grundlage erweitern, indem wir Navigation, Struktur und Daten hinzufügen.

Umfang

In diesem Codelab erstellen Sie einen Startbildschirm für eine App namens Shrine, eine E-Commerce-App, in der Kleidung und Haushaltswaren verkauft werden. Sie enthält:

  • Eine obere App-Leiste
  • Eine Rasterliste mit Produkten

Android

iOS

E‑Commerce-App mit einer oberen App-Leiste und einem Raster voller Produkte

E‑Commerce-App mit einer oberen App-Leiste und einem Raster voller Produkte

Material-Flutter-Komponenten und ‑Subsysteme in diesem Codelab

  • Obere App-Leiste
  • Raster
  • Karten

Wie würden Sie Ihre Erfahrung mit der Flutter-Entwicklung bewerten?

Anfänger Mittelstufe Fortgeschritten

2. Flutter-Entwicklungsumgebung einrichten

Für dieses Lab benötigen Sie zwei Softwarekomponenten: das Flutter SDK und einen Editor.

Sie können das Codelab auf einem der folgenden Geräte ausführen:

  • Ein physisches Android- oder iOS-Gerät, das mit Ihrem Computer verbunden ist und auf den Entwicklermodus eingestellt ist.
  • Der iOS-Simulator (erfordert die Installation von Xcode-Tools).
  • Android Emulator (Einrichtung in Android Studio erforderlich)
  • Ein Browser (für das Debugging ist Chrome erforderlich).
  • Als Windows-, Linux- oder macOS-Desktopanwendung. Sie müssen auf der Plattform entwickeln, auf der Sie die Bereitstellung planen. Wenn Sie also eine Windows-Desktop-App entwickeln möchten, müssen Sie unter Windows entwickeln, um auf die entsprechende Build-Kette zuzugreifen. Es gibt betriebssystemspezifische Anforderungen, die auf docs.flutter.dev/desktop ausführlich beschrieben werden.

3. Starter-App für das Codelab herunterladen

Sie machen mit MDC-101 weiter?

Wenn Sie MDC-101 abgeschlossen haben, sollte Ihr Code für dieses Codelab vorbereitet sein. Direkt zum Schritt „Obere App-Leiste hinzufügen“

Sie fangen bei null an?

Starter-App für das Codelab herunterladen

Die Starter-App befindet sich im Verzeichnis material-components-flutter-codelabs-102-starter_and_101-complete/mdc_100_series.

…oder aus GitHub klonen

Führen Sie die folgenden Befehle aus, um dieses Codelab von GitHub zu klonen:

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

Projekt öffnen und App ausführen

  1. Öffnen Sie das Projekt in einem beliebigen Editor.
  2. Folgen Sie der Anleitung unter Erste Schritte: Testlauf für den von Ihnen ausgewählten Editor.

Fertig! Auf Ihrem Gerät sollte die Shrine-Anmeldeseite aus dem MDC-101-Codelab angezeigt werden.

Android

iOS

Anmeldeseite mit Feldern für Nutzername und Passwort sowie Schaltflächen zum Abbrechen und Weiter

Anmeldeseite mit Feldern für Nutzername und Passwort sowie Schaltflächen zum Abbrechen und Weiter

Nachdem der Anmeldebildschirm gut aussieht, füllen wir die App mit einigen Produkten.

4. Obere App-Leiste hinzufügen

Wenn Sie jetzt auf die Schaltfläche „Weiter“ klicken, wird der Startbildschirm mit der Meldung „Du hast es geschafft!“ angezeigt. Sehr gut. Der Nutzer kann jetzt aber keine Aktionen ausführen und weiß auch nicht, wo er sich in der App befindet. Um ihm zu helfen, müssen wir die Navigation hinzufügen.

Material Design bietet Navigationsmuster, die für eine hohe Benutzerfreundlichkeit sorgen. Eine der sichtbarsten Komponenten ist die obere App-Leiste.

Um die Navigation zu ermöglichen und Nutzern schnellen Zugriff auf andere Aktionen zu geben, fügen wir eine obere App-Leiste hinzu.

AppBar-Widget hinzufügen

Fügen Sie in home.dart eine AppBar zum Scaffold hinzu und entfernen Sie die hervorgehobene const:

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

Wenn wir die AppBar dem appBar:-Feld des Scaffold hinzufügen, erhalten wir ein perfektes Layout, bei dem die AppBar oben auf der Seite und der Body darunter platziert werden.

Textwidget hinzufügen

Fügen Sie in home.dart einen Titel für die AppBar hinzu:

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

Projekt speichern.

Android

iOS

Eine App-Leiste mit „Shrine“ als Titel

Eine App-Leiste mit „Shrine“ als Titel

Viele App-Leisten haben eine Schaltfläche neben dem Titel. Fügen wir unserer App ein Menüsymbol hinzu.

Führendes IconButton hinzufügen

Legen Sie in home.dart für das Feld leading: der AppBar ein IconButton fest. (Fügen Sie es vor dem Feld title: ein, um die Reihenfolge von führend nach nachfolgend zu simulieren):

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

Projekt speichern.

Android

iOS

Eine App-Leiste mit „Shrine“ als Titel und einem Hamburger-Menüsymbol

Eine App-Leiste mit „Shrine“ als Titel und einem Hamburger-Menüsymbol

Das Menüsymbol (auch als „Hamburger“ bezeichnet) wird an der erwarteten Stelle angezeigt.

Sie können auch Schaltflächen auf der rechten Seite des Titels hinzufügen. In Flutter werden diese als „Aktionen“ bezeichnet.

Aktionen hinzufügen

Es ist noch Platz für zwei weitere IconButtons.

Fügen Sie sie nach dem Titel der AppBar-Instanz hinzu:

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

Projekt speichern. Ihr Startbildschirm sollte so aussehen:

Android

iOS

Eine App-Leiste mit „Shrine“ als Titel und einem Hamburger-Menüsymbol sowie nachfolgenden Such- und Anpassungssymbolen

Eine App-Leiste mit „Shrine“ als Titel und einem Hamburger-Menüsymbol sowie nachfolgenden Such- und Anpassungssymbolen

Die App hat jetzt eine führende Schaltfläche, einen Titel und zwei Aktionen auf der rechten Seite. In der App-Leiste wird auch die Erhebung durch einen dezenten Schatten dargestellt, der zeigt, dass sie sich auf einer anderen Ebene als die Inhalte befindet.

5. Karte in einem Raster hinzufügen

Nachdem wir unserer App eine Struktur gegeben haben, organisieren wir die Inhalte, indem wir sie in Karten platzieren.

GridView hinzufügen

Fügen wir zuerst eine Karte unter der oberen App-Leiste hinzu. Das Card-Widget allein enthält nicht genügend Informationen, um sich so zu positionieren, dass es sichtbar ist. Daher müssen wir es in ein GridView-Widget einbetten.

Ersetzen Sie das Center im Body des Scaffolds durch ein 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()],
),

Sehen wir uns den Code genauer an. GridView ruft den count()-Konstruktor auf, da die Anzahl der angezeigten Elemente zählbar und nicht unendlich ist. Für das Layout sind jedoch weitere Informationen erforderlich.

Der Parameter crossAxisCount: gibt an, wie viele Elemente nebeneinander angezeigt werden. Wir möchten zwei Spalten.

Das Feld padding: bietet Platz auf allen vier Seiten des GridView. Das Padding auf der nachfolgenden oder unteren Seite ist natürlich nicht zu sehen, da sich daneben noch keine GridView-Untergeordneten befinden.

Das Feld childAspectRatio: gibt die Größe der Elemente basierend auf einem Seitenverhältnis (Breite über Höhe) an.

Standardmäßig werden in GridView Kacheln mit derselben Größe erstellt.

Wir haben eine Karte, aber sie ist leer. Fügen wir der Karte nun untergeordnete Widgets hinzu.

Inhalte anordnen

Infokarten sollten Bereiche für ein Bild, einen Titel und sekundären Text haben.

Aktualisieren Sie die untergeordneten Elemente von 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'),
            ],
          ),
        ),
      ],
    ),
  )
],

Mit diesem Code wird ein Column-Widget hinzugefügt, mit dem die untergeordneten Widgets vertikal angeordnet werden.

Mit crossAxisAlignment: field wird CrossAxisAlignment.start angegeben, was bedeutet, dass der Text am linken Rand ausgerichtet wird.

Das AspectRatio-Widget bestimmt die Form des Bildes, unabhängig davon, welche Art von Bild bereitgestellt wird.

Mit Padding wird der Text etwas von der Seite eingerückt.

Die beiden Text-Widgets sind vertikal gestapelt und haben einen Abstand von 8 Punkten (SizedBox). Wir erstellen eine weitere Spalte, um sie im Padding unterzubringen.

Projekt speichern.

Android

iOS

ein einzelnes Element mit einem Bild, einem Titel und sekundärem Text

ein einzelnes Element mit einem Bild, einem Titel und sekundärem Text

In dieser Vorschau ist die Karte vom Rand eingerückt, hat abgerundete Ecken und einen Schatten, der die Höhe der Karte angibt. Die gesamte Form wird in Material als „Container“ bezeichnet. Nicht zu verwechseln mit der eigentlichen Widget-Klasse Container.

Karten werden normalerweise in einer Sammlung mit anderen Karten angezeigt. Wir können sie als Sammlung in einem Raster anordnen.

6. Kartensammlung erstellen

Wenn auf einem Bildschirm mehrere Karten vorhanden sind, werden sie in einer oder mehreren Sammlungen gruppiert. Die Karten in einer Sammlung sind koplanar, d. h., sie haben alle dieselbe Höhe (es sei denn, sie werden aufgenommen oder gezogen, was hier aber nicht der Fall ist).

Karte in eine Sammlung einfügen

Derzeit wird unsere Karte inline mit dem Feld children: der GridView erstellt. Das ist viel verschachtelter Code, der schwer zu lesen sein kann. Wir extrahieren sie in eine Funktion, die beliebig viele leere Karten generieren und eine Liste mit Karten zurückgeben kann.

Erstellen Sie eine neue private Funktion über der Funktion build(). Denken Sie daran, dass Funktionen, die mit einem Unterstrich beginnen, private APIs sind:

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

Weisen Sie die generierten Karten dem Feld children von GridView zu. Ersetzen Sie alles, was in der GridView enthalten ist, durch diesen neuen 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
),

Projekt speichern.

Android

iOS

Ein Raster mit Elementen, die ein Bild, einen Titel und sekundären Text enthalten

Ein Raster mit Elementen, die ein Bild, einen Titel und sekundären Text enthalten

Die Karten sind vorhanden, aber es wird noch nichts angezeigt. Jetzt ist es an der Zeit, Produktdaten hinzuzufügen.

Produktdaten hinzufügen

Die App enthält einige Produkte mit Bildern, Namen und Preisen. Fügen wir das den Widgets hinzu, die wir bereits auf der Karte haben.

Importieren Sie dann in home.dart ein neues Paket und einige Dateien, die wir für ein Datenmodell bereitgestellt haben:

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

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

Ändern Sie schließlich _buildGridCards(), um die Produktinformationen abzurufen, und verwenden Sie diese Daten in den Karten:

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

HINWEIS:Der Code wird noch nicht kompiliert und ausgeführt. Wir haben noch eine Änderung.

Ändern Sie außerdem die Funktion build() so, dass der BuildContext an _buildGridCards() übergeben wird, bevor Sie versuchen, den Code zu kompilieren:

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

Führe einen Hot Restart der App durch.

Android

iOS

ein Raster mit Artikeln, die jeweils ein Bild, einen Produkttitel und einen Preis enthalten

ein Raster mit Artikeln, die jeweils ein Bild, einen Produkttitel und einen Preis enthalten

Wir fügen keinen vertikalen Abstand zwischen den Karten ein. Das liegt daran, dass sie standardmäßig oben und unten einen Rand von 4 Punkten haben.

Projekt speichern.

Die Produktdaten werden angezeigt, aber die Bilder haben zusätzlichen Leerraum. Die Bilder werden standardmäßig mit einem BoxFit von .scaleDown gezeichnet (in diesem Fall). Ändern wir das in .fitWidth, damit die Bilder etwas vergrößert und die zusätzlichen Leerzeichen entfernt werden.

Fügen Sie dem Bild das Feld fit: mit dem Wert BoxFit.fitWidth hinzu:

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

Android

iOS

Ein Raster mit Artikeln, die jeweils ein zugeschnittenes Bild, einen Produkttitel und einen Preis enthalten

Unsere Produkte werden jetzt perfekt in der App angezeigt.

7. Glückwunsch!

Unsere App hat einen einfachen Ablauf, der den Nutzer vom Anmeldebildschirm zu einem Startbildschirm führt, auf dem Produkte angezeigt werden. Mit nur wenigen Codezeilen haben wir eine obere App-Leiste (mit einem Titel und drei Schaltflächen) und Karten (zur Präsentation der Inhalte unserer App) hinzugefügt. Unser Startbildschirm ist jetzt einfach und funktional, mit einer grundlegenden Struktur und umsetzbaren Inhalten.

Weiteres Vorgehen

Mit der oberen App-Leiste, der Karte, dem Textfeld und der Schaltfläche haben wir nun vier Kernkomponenten aus der Material-Flutter-Bibliothek verwendet. Weitere Informationen finden Sie im Katalog der Material-Komponenten-Widgets.

Sie ist zwar voll funktionsfähig, aber es wird noch keine bestimmte Marke oder Meinung vertreten. In MDC-103: Material Design Theming with Color, Shape, Elevation and Type (MDC-103: Material Design-Theming mit Farbe, Form, Höhe und Typ) passen wir den Stil dieser Komponenten an, um eine lebendige, moderne Marke zu präsentieren.

Ich konnte dieses Codelab in angemessener Zeit und mit angemessenem Aufwand durcharbeiten.

Stimme vollkommen zu Stimme zu Neutral Stimme nicht zu Stimme überhaupt nicht zu

Ich möchte Material Components auch in Zukunft verwenden.

Stimme voll und ganz zu Stimme zu Neutral Stimme nicht zu Stimme überhaupt nicht zu