Deine erste Flutter-App

1. Einführung

Flutter ist das UI-Toolkit von Google für die Entwicklung von mobilen, Web- und Desktopanwendungen mit einer einzigen Codebasis. In diesem Codelab erstellen Sie die folgende Flutter-Anwendung:

Die Anwendung generiert kühle Namen wie "newstay", "lightstream", "mainbrake" oder "graypine". Der Nutzer kann nach dem nächsten Namen fragen, den aktuellen als Favoriten hinzufügen und die Liste der Favoriten auf einer separaten Seite ansehen. Die App reagiert auf verschiedene Bildschirmgrößen.

Lerninhalte

  • Grundlagen der Funktionsweise von Flutter
  • Layouts in Flutter erstellen
  • Verbindung zwischen Nutzerinteraktionen (z. B. Drücken von Tasten) und App-Verhalten herstellen
  • Flutter-Code organisieren
  • Ihre App responsiv machen (für verschiedene Bildschirme)
  • Ein einheitliches Erscheinungsbild die Funktionsweise Ihrer App

Sie beginnen mit einem einfachen Gerüst, sodass Sie direkt zu den interessanten Stellen springen können.

e9c6b402cd8003fd.png

Hier ist Filip, der Sie durch das gesamte Codelab führt!

Klicken Sie auf „Weiter“, um das Lab zu starten.

2. Flutter-Umgebung einrichten

Editor

Um dieses Codelab so einfach wie möglich zu gestalten, gehen wir davon aus, dass Sie Visual Studio Code (VS Code) als Entwicklungsumgebung verwenden. Es ist kostenlos und funktioniert auf allen gängigen Plattformen.

Natürlich können Sie einen beliebigen Editor verwenden: Android Studio, andere IntelliJ IDEs, Emacs, Vim oder Notepad++. Sie alle sind mit Flutter kompatibel.

Wir empfehlen, für dieses Codelab VS Code zu verwenden, da die Anleitung standardmäßig VS Code-spezifische Tastenkombinationen verwendet. Es ist einfacher, Dinge wie „Klicken Sie hier“ zu sagen oder „Diese Taste drücken“ anstelle von etwas wie „Führen Sie im Editor die entsprechende Aktion aus, um X zu tun“.

228c71510a8e868.png

Entwicklungsziel auswählen

Flutter ist ein plattformübergreifendes Toolkit. Ihre App kann unter einem der folgenden Betriebssysteme ausgeführt werden:

  • iOS
  • Android
  • Windows
  • macOS
  • Linux
  • web

Üblicherweise wählen Sie jedoch ein einziges Betriebssystem aus, für das Sie hauptsächlich entwickeln. Dies ist Ihr „Entwicklungsziel“, also das Betriebssystem, auf dem Ihre App während der Entwicklung ausgeführt wird.

16695777c07f18e5.png

Angenommen, Sie verwenden einen Windows-Laptop, um eine Flutter-App zu entwickeln. Wenn Sie Android als Entwicklungsziel auswählen, schließen Sie in der Regel ein Android-Gerät über ein USB-Kabel an Ihren Windows-Laptop an. Ihre App in der Entwicklung wird auf diesem angeschlossenen Android-Gerät ausgeführt. Sie können aber auch Windows als Entwicklungsziel auswählen. In diesem Fall wird Ihre App in der Entwicklung als Windows-App zusammen mit Ihrem Editor ausgeführt.

Es kann verlockend sein, das Web als Entwicklungsziel auszuwählen. Der Nachteil dieser Auswahl ist, dass Ihnen eine der nützlichsten Entwicklungsfunktionen von Flutter verloren geht: das zustandsorientierte Hot Refresh. Flutter kann Webanwendungen nicht Hot-Reloads ausführen.

Treffen Sie jetzt Ihre Entscheidung. Denken Sie daran: Sie können Ihre App später jederzeit auch auf anderen Betriebssystemen ausführen. Es ist nur, dass ein klares Entwicklungsziel den nächsten Schritt reibungsloser gestaltet.

Flutter installieren

Eine aktuelle Anleitung zur Installation des Flutter SDK finden Sie unter docs.flutter.dev.

Die Anleitung auf der Flutter-Website bezieht sich nicht nur auf die Installation des SDK selbst, sondern auch auf die Entwicklungstools und die Editor-Plug-ins. Für dieses Codelab müssen Sie nur Folgendes installieren:

  1. Flutter-SDK
  2. Visual Studio Code mit dem Flutter-Plug-in
  3. Die für das ausgewählte Entwicklungsziel erforderliche Software (z. B. Visual Studio für Windows oder Xcode für macOS)

Im nächsten Abschnitt erstellen Sie Ihr erstes Flutter-Projekt.

Wenn Sie bisher Probleme hatten, könnten einige dieser Fragen und Antworten (von StackOverflow) für die Fehlerbehebung hilfreich sein.

FAQ

3. Projekt erstellen

Erstes Flutter-Projekt erstellen

Starten Sie Visual Studio Code und öffnen Sie die Befehlspalette (mit F1, Ctrl+Shift+P oder Shift+Cmd+P). Geben Sie „flutter new“ ein. Wählen Sie den Befehl Flutter: New Project (Flutter: Neues Projekt) aus.

Wählen Sie als Nächstes Anwendung und dann einen Ordner aus, in dem Sie Ihr Projekt erstellen möchten. Das könnte Ihr Basisverzeichnis oder etwa C:\src\ sein.

Geben Sie zum Schluss einen Namen für Ihr Projekt ein. Zum Beispiel namer_app oder my_awesome_namer.

260a7d97f9678005.png

Flutter erstellt nun Ihren Projektordner und öffnet ihn in VS Code.

Sie überschreiben nun den Inhalt von drei Dateien mit einem einfachen Gerüst der App.

Kopieren und Erste App einfügen

Achten Sie darauf, dass im linken Bereich von VS Code Explorer ausgewählt ist, und öffnen Sie die Datei pubspec.yaml.

e2a5bab0be07f4f7.png

Ersetzen Sie den Inhalt dieser Datei durch Folgendes:

pubspec.yaml

name: namer_app
description: A new Flutter project.

publish_to: 'none' # Remove this line if you wish to publish to pub.dev

version: 0.0.1+1

environment:
  sdk: ^3.1.1

dependencies:
  flutter:
    sdk: flutter

  english_words: ^4.0.0
  provider: ^6.0.0

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^2.0.0

flutter:
  uses-material-design: true

In der Datei pubspec.yaml sind grundlegende Informationen zu deiner App angegeben, z. B. die aktuelle Version, die Abhängigkeiten sowie die Assets, mit denen sie ausgeliefert wird.

Öffnen Sie als Nächstes eine weitere Konfigurationsdatei im Projekt: analysis_options.yaml.

a781f218093be8e0.png

Ersetzen Sie den Inhalt durch Folgendes:

analysis_options.yaml

include: package:flutter_lints/flutter.yaml

linter:
  rules:
    avoid_print: false
    prefer_const_constructors_in_immutables: false
    prefer_const_constructors: false
    prefer_const_literals_to_create_immutables: false
    prefer_final_fields: false
    unnecessary_breaks: true
    use_key_in_widget_constructors: false

Mit dieser Datei wird festgelegt, wie streng Flutter bei der Analyse Ihres Codes sein sollte. Da dies Ihr erster Ausflug in Flutter ist, weisen Sie das Analysegerät an, es ganz entspannt anzugehen. Du kannst dies später jederzeit anpassen. Je näher Sie der Veröffentlichung einer echten Produktions-App kommen, desto strenger werden Sie den Analysator.

Öffnen Sie schließlich die Datei main.dart im Verzeichnis lib/.

e54c671c9bb4d23d.png

Ersetzen Sie den Inhalt dieser Datei durch Folgendes:

lib/main.dart

import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => MyAppState(),
      child: MaterialApp(
        title: 'Namer App',
        theme: ThemeData(
          useMaterial3: true,
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
        ),
        home: MyHomePage(),
      ),
    );
  }
}

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();

    return Scaffold(
      body: Column(
        children: [
          Text('A random idea:'),
          Text(appState.current.asLowerCase),
        ],
      ),
    );
  }
}

Diese 50 Codezeilen sind die gesamte App.

Im nächsten Abschnitt führen Sie die Anwendung im Debug-Modus aus und beginnen mit der Entwicklung.

4. Schaltfläche hinzufügen

Dadurch wird die Schaltfläche Weiter hinzugefügt, mit der eine neue Wortkopplung generiert werden kann.

App starten

Öffne zuerst lib/main.dart und prüfe, ob du dein Zielgerät ausgewählt hast. Unten rechts in VS Code finden Sie eine Schaltfläche, über die das aktuelle Zielgerät angezeigt wird. Zum Ändern klicken.

Während lib/main.dart geöffnet ist, kannst du nach „Wiedergabe“ suchen b0a5d0200af5985d.png rechts oben im VS Code-Fenster und klicken Sie darauf.

Nach etwa einer Minute wird die App im Debug-Modus gestartet. Es sieht noch nicht viel aus:

f96e7dfb0937d7f4.png

First Hot Refresh

Fügen Sie dem String im ersten Text-Objekt unten in lib/main.dart etwas hinzu und speichern Sie die Datei (mit Ctrl+S oder Cmd+S). Hier einige Beispiele:

lib/main.dart

// ...

    return Scaffold(
      body: Column(
        children: [
          Text('A random AWESOME idea:'),  // ← Example change.
          Text(appState.current.asLowerCase),
        ],
      ),
    );

// ...

Die App ändert sich sofort, aber das Wort bleibt gleich. Dies ist das berühmte zustandsorientierte Hot Refresh von Flutter. Ein Hot Refresh wird ausgelöst, wenn Sie Änderungen an einer Quelldatei speichern.

Häufig gestellte Fragen

Schaltfläche hinzufügen

Fügen Sie als Nächstes eine Schaltfläche am unteren Rand von Column direkt unter der zweiten Text-Instanz hinzu.

lib/main.dart

// ...

    return Scaffold(
      body: Column(
        children: [
          Text('A random AWESOME idea:'),
          Text(appState.current.asLowerCase),

          // ↓ Add this.
          ElevatedButton(
            onPressed: () {
              print('button pressed!');
            },
            child: Text('Next'),
          ),

        ],
      ),
    );

// ...

Wenn Sie die Änderung speichern, wird die App wieder aktualisiert: Eine Schaltfläche wird angezeigt und in der Debug Console in VS Code wird die Meldung Schaltfläche gedrückt! angezeigt.

Ein Flutter Crashkurs in 5 Minuten

So viel Spaß es auch macht, die Debugging-Konsole anzusehen, Sie möchten, dass die Schaltfläche etwas aussagekräftigeres tun kann. Bevor Sie damit beginnen, werfen Sie jedoch einen genaueren Blick auf den Code in lib/main.dart, um zu verstehen, wie er funktioniert.

lib/main.dart

// ...

void main() {
  runApp(MyApp());
}

// ...

Ganz oben in der Datei finden Sie die Funktion main(). In der aktuellen Form wird Flutter nur angewiesen, die in MyApp definierte App auszuführen.

lib/main.dart

// ...

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => MyAppState(),
      child: MaterialApp(
        title: 'Namer App',
        theme: ThemeData(
          useMaterial3: true,
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
        ),
        home: MyHomePage(),
      ),
    );
  }
}

// ...

Die Klasse MyApp erweitert StatelessWidget. Widgets sind die Elemente, aus denen Sie jede Flutter-App erstellen. Wie Sie sehen, ist sogar die App selbst ein Widget.

Mit dem Code in MyApp wird die gesamte App eingerichtet. Damit wird der App-weite Status erstellt (mehr dazu später), der App einen Namen, das visuelle Design und das „Home“. Widget, also den Startpunkt Ihrer App.

lib/main.dart

// ...

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();
}

// ...

Als Nächstes definiert die Klasse MyAppState den Status der App. Dies ist dein erster Schritt in Flutter. Deshalb wird es in diesem Codelab einfach und fokussiert bleiben. Es gibt viele leistungsstarke Möglichkeiten, den App-Status in Flutter zu verwalten. Eine der einfachsten Erklärungen ist ChangeNotifier, der Ansatz dieser App.

  • MyAppState definiert die Daten, die die App benötigt. Derzeit enthält es nur eine einzige Variable mit dem aktuellen Zufallswortpaar. Dieses Feld wird später hinzugefügt.
  • Die Statusklasse erweitert ChangeNotifier, was bedeutet, dass sie andere über ihre eigenen Änderungen benachrichtigen kann. Wenn sich beispielsweise das aktuelle Wortpaar ändert, müssen einige Widgets in der App darüber informiert werden.
  • Der Status wird erstellt und mithilfe eines ChangeNotifierProvider für die gesamte Anwendung bereitgestellt (siehe Code oben in MyApp). Dadurch kann jedes Widget in der App den Status abrufen. d9b6ecac5494a6ff.png

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {           // ← 1
    var appState = context.watch<MyAppState>();  // ← 2

    return Scaffold(                             // ← 3
      body: Column(                              // ← 4
        children: [
          Text('A random AWESOME idea:'),        // ← 5
          Text(appState.current.asLowerCase),    // ← 6
          ElevatedButton(
            onPressed: () {
              print('button pressed!');
            },
            child: Text('Next'),
          ),
        ],                                       // ← 7
      ),
    );
  }
}

// ...

Schließlich gibt es noch MyHomePage, das Widget, das Sie bereits geändert haben. Jede nummerierte Zeile unten entspricht einem Zeilennummernkommentar im obigen Code:

  1. Jedes Widget definiert eine build()-Methode, die automatisch aufgerufen wird, wenn sich die Umstände des Widgets ändern, damit das Widget immer auf dem neuesten Stand ist.
  2. MyHomePage verfolgt Änderungen am aktuellen Status der App mit der Methode watch.
  3. Jede build-Methode muss ein Widget oder – üblicher – eine verschachtelte Struktur von Widgets zurückgeben. In diesem Fall ist das Widget der obersten Ebene Scaffold. In diesem Codelab wirst du nicht mit Scaffold arbeiten. Es ist aber ein nützliches Widget, das in den meisten realen Flutter-Apps zu finden ist.
  4. Column ist eines der einfachsten Layout-Widgets in Flutter. Dabei werden eine beliebige Anzahl von untergeordneten Elementen von oben nach unten in eine Spalte eingefügt. Standardmäßig werden die untergeordneten Elemente der Spalte visuell oben platziert. Bald werden Sie dies ändern, sodass die Spalte zentriert wird.
  5. Sie haben dieses Text-Widget im ersten Schritt geändert.
  6. Dieses zweite Text-Widget verwendet appState und greift auf das einzige Mitglied dieser Klasse zu, current (also eine WordPair). WordPair bietet mehrere hilfreiche Getter, z. B. asPascalCase oder asSnakeCase. Hier verwenden wir asLowerCase, aber Sie können dies jetzt ändern, wenn Sie eine der Alternativen bevorzugen.
  7. Beachten Sie, dass nachgestellte Kommas im Flutter-Code intensiv eingesetzt werden. Dieses Komma muss hier nicht stehen, da children das letzte (und einzig) Mitglied dieser speziellen Column-Parameterliste ist. Im Allgemeinen empfiehlt es sich jedoch, nachgestellte Kommas zu verwenden: Sie machen das Hinzufügen weiterer Mitglieder zum Kinderspiel und sie dienen dem automatischen Formatierer von Dart als Hinweis, dort eine neue Zeile zu platzieren. Weitere Informationen finden Sie unter Codeformatierung.

Als Nächstes verbinden Sie die Schaltfläche mit dem Status.

Ihr erstes Verhalten

Scrollen Sie zu MyAppState und fügen Sie eine getNext-Methode hinzu.

lib/main.dart

// ...

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();

  // ↓ Add this.
  void getNext() {
    current = WordPair.random();
    notifyListeners();
  }
}

// ...

Die neue Methode getNext() weist current mit einer neuen zufälligen WordPair neu zu. Außerdem wird notifyListeners() aufgerufen. Diese Methode von ChangeNotifier) sorgt dafür, dass alle Zuschauer von MyAppState benachrichtigt werden.

Nun müssen Sie nur noch die getNext-Methode über den Callback der Schaltfläche aufrufen.

lib/main.dart

// ...

    ElevatedButton(
      onPressed: () {
        appState.getNext();  // ← This instead of print().
      },
      child: Text('Next'),
    ),

// ...

Speichern und die App jetzt testen. Jedes Mal, wenn Sie auf die Schaltfläche Weiter klicken, sollte ein neues Zufallswortpaar generiert werden.

Im nächsten Abschnitt gestalten Sie die Benutzeroberfläche übersichtlicher.

5. App ansprechender gestalten

So sieht die App im Moment aus.

3dd8a9d8653bdc56.png

Nicht so toll. Das Herzstück der App – das zufällig generierte Wortpaar – sollte besser sichtbar sein. Das ist schließlich der Hauptgrund dafür, dass unsere Nutzer diese App verwenden! Außerdem sind die App-Inhalte seltsam nicht mittig platziert und die gesamte App ist langweilig und schwarz. weiß.

In diesem Abschnitt wird das Design der App überarbeitet, um diese Probleme zu beheben. Das Endziel für diesen Abschnitt sieht in etwa so aus:

2bbee054d81a3127.png

Widget extrahieren

Die Zeile, in der das aktuelle Wortpaar angezeigt wird, sieht jetzt so aus: Text(appState.current.asLowerCase). Für eine komplexere Umwandlung empfiehlt es sich, diese Zeile in ein separates Widget zu extrahieren. Die Verwendung separater Widgets für separate logische Teile Ihrer Benutzeroberfläche ist ein wichtiger Schritt, um die Komplexität in Flutter zu bewältigen.

Flutter bietet eine Refaktorierungshilfe zum Extrahieren von Widgets. Achten Sie jedoch vor der Verwendung darauf, dass die extrahierte Zeile nur auf die benötigten Informationen zugreift. Im Moment greift die Zeile auf appState zu. Tatsächlich muss aber nur das aktuelle Wortpaar angegeben werden.

Schreiben Sie deshalb das MyHomePage-Widget so um:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();  
    var pair = appState.current;                 // ← Add this.

    return Scaffold(
      body: Column(
        children: [
          Text('A random AWESOME idea:'),
          Text(pair.asLowerCase),                // ← Change to this.
          ElevatedButton(
            onPressed: () {
              appState.getNext();
            },
            child: Text('Next'),
          ),
        ],
      ),
    );
  }
}

// ...

Schön. Das Text-Widget bezieht sich nicht mehr auf die gesamte appState.

Rufen Sie jetzt das Menü Refaktorierung auf. In VS Code haben Sie dafür zwei Möglichkeiten:

  1. Klicken Sie mit der rechten Maustaste auf den Code, den Sie refaktorieren möchten (in diesem Fall Text), und wählen Sie im Drop-down-Menü Refaktorieren... aus.

ODER

  1. Bewegen Sie den Cursor zu dem Code, den Sie refaktorieren möchten (in diesem Fall Text) und drücken Sie Ctrl+. (Windows/Linux) oder Cmd+. (Mac).

Wählen Sie im Menü Refaktorieren die Option Widget extrahieren aus. Weisen Sie einen Namen zu, z. B. BigCard und klicken Sie auf Enter.

Dadurch wird am Ende der aktuellen Datei automatisch eine neue Klasse namens BigCard erstellt. Der Kurs sieht in etwa so aus:

lib/main.dart

// ...

class BigCard extends StatelessWidget {
  const BigCard({
    super.key,
    required this.pair,
  });

  final WordPair pair;

  @override
  Widget build(BuildContext context) {
    return Text(pair.asLowerCase);
  }
}

// ...

Beachten Sie, dass die App auch während dieser Refaktorierung weiterhin funktioniert.

Hinzufügen einer Karte

Jetzt ist es an der Zeit, dieses neue Widget in die fett gedruckte Benutzeroberfläche einzubinden, die wir am Anfang dieses Abschnitts vorgestellt haben.

Suchen Sie die Klasse BigCard und die darin enthaltene Methode build(). Rufen Sie wie zuvor das Menü Refaktorieren im Text-Widget auf. In diesem Fall werden Sie das Widget jedoch nicht extrahieren.

Wählen Sie stattdessen Mit Füllung umschließen aus. Dadurch wird ein neues übergeordnetes Widget mit dem Namen Padding um das Text-Widget erstellt. Nach dem Speichern sehen Sie, dass das zufällige Wort bereits mehr Raum hat.

Erhöhen Sie den Abstand ausgehend vom Standardwert 8.0. Verwenden Sie beispielsweise 20, um einen größeren Abstand zu erreichen.

Geh als Nächstes eine Ebene höher. Bewegen Sie den Mauszeiger auf das Padding-Widget, öffnen Sie das Menü Refaktorierung und wählen Sie Mit Widget umschließen... aus.

Damit können Sie das übergeordnete Widget angeben. Gib „Card“ ein. und drücken Sie die Eingabetaste.

Dadurch werden das Padding-Widget und damit auch das Text mit einem Card-Widget umschlossen.

6031adbc0a11e16b.png

Design und Stil

Um die Karte stärker hervorzuheben, zeichnen Sie sie mit einer kräftigen Farbe. Da es außerdem immer sinnvoll ist, ein einheitliches Farbschema beizubehalten, verwenden Sie die Theme der App, um die Farbe auszuwählen.

Nehmen Sie die folgenden Änderungen an der Methode build() von BigCard vor.

lib/main.dart

// ...

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);       // ← Add this.

    return Card(
      color: theme.colorScheme.primary,    // ← And also this.
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Text(pair.asLowerCase),
      ),
    );
  }

// ...

Diese beiden neuen Zeilen leisten viel Arbeit:

  • Zuerst fordert der Code das aktuelle Design der App mit Theme.of(context) an.
  • Dann definiert der Code die Farbe der Karte so, dass sie der Farbe der colorScheme-Eigenschaft des Designs entspricht. Das Farbschema enthält viele Farben. primary ist die markanteste und prägendste Farbe der App.

Die Karte ist jetzt in der Hauptfarbe der App dargestellt:

a136f7682c204ea1.png

Du kannst diese Farbe und das Farbschema der gesamten App ändern, indem du nach oben zu MyApp scrollst und dort die Startfarbe für ColorScheme änderst.

Achten Sie darauf, wie die Farbe reibungslos animiert wird. Dies wird als implizite Animation bezeichnet. Viele Flutter-Widgets interpolieren nahtlos zwischen Werten, sodass die Benutzeroberfläche nicht einfach „hüpft“. zwischen Bundesstaaten.

Die Schaltfläche „Hoch“ unter der Karte ändert ihre Farbe. Das ist die Leistungsfähigkeit einer App-weiten Theme im Gegensatz zu fest codierten Werten.

TextTheme

Es gibt immer noch ein Problem mit der Karte: Der Text ist zu klein und die Farbe ist schwer zu lesen. Nehmen Sie die folgenden Änderungen an der Methode build() von BigCard vor, um das Problem zu beheben.

lib/main.dart

// ...

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    // ↓ Add this.
    final style = theme.textTheme.displayMedium!.copyWith(
      color: theme.colorScheme.onPrimary,
    );

    return Card(
      color: theme.colorScheme.primary,
      child: Padding(
        padding: const EdgeInsets.all(20),
        // ↓ Change this line.
        child: Text(pair.asLowerCase, style: style),
      ),
    );
  }

// ...

Was steckt hinter dieser Änderung?

  • Mit theme.textTheme, greifst du auf das Schriftdesign der App zu. Dieser Kurs umfasst Mitglieder wie bodyMedium (für Standardtext mittlerer Größe), caption (für Bilduntertitel) oder headlineLarge (für große Anzeigentitel).
  • Die Eigenschaft displayMedium ist ein großer Stil, der für Anzeigetext vorgesehen ist. Das Wort Display wird hier im typografischen Sinn verwendet, z. B. als Anzeigeschrift. Die Dokumentation für displayMedium besagt, dass „Darstellungsstile für kurzen, wichtigen Text reserviert sind“ – genau unser Anwendungsfall.
  • Die displayMedium-Eigenschaft des Designs könnte theoretisch null sein. Dart, die Programmiersprache, in der Sie diese App schreiben, ist null-sicher, sodass Sie keine Methoden von Objekten aufrufen können, die möglicherweise null sind. In diesem Fall können Sie jedoch den !-Operator („Bang-Operator“) verwenden, damit Dart wissen, was Sie tun. (displayMedium ist in diesem Fall definitiv nicht null. Wir wissen aber, dass dies den Rahmen dieses Codelabs sprengen würde.)
  • Wenn Sie copyWith() für displayMedium aufrufen, wird eine Kopie des Textstils mit den von Ihnen definierten Änderungen zurückgegeben. In diesem Fall ändern Sie nur die Textfarbe.
  • Um die neue Farbe zu erhalten, müssen Sie wieder auf das Design der App zugreifen. Die Eigenschaft onPrimary des Farbschemas definiert eine Farbe, die sich gut zur Verwendung derHauptfarbe der Appder App eignet.

Die App sollte jetzt in etwa so aussehen:

2405e9342d28c193.png

Wenn Sie möchten, ändern Sie die Karte weiter. Hier einige Vorschläge:

  • Mit copyWith() können Sie nicht nur die Farbe, sondern auch den Textstil sehr viel mehr ändern. Um die vollständige Liste der Eigenschaften zu erhalten, die Sie ändern können, setzen Sie den Cursor an eine beliebige Stelle in die Klammern von copyWith() und drücken Sie Ctrl+Shift+Space (Windows/Linux) oder Cmd+Shift+Space (Mac).
  • Analog kannst du weitere Änderungen am Card-Widget vornehmen. Sie können beispielsweise den Schatten der Karte vergrößern, indem Sie den Wert des Parameters elevation erhöhen.
  • Experimentiere mit Farben. Neben theme.colorScheme.primary gibt es auch .secondary, .surface und viele weitere. Für alle diese Farben gibt es eine onPrimary-Entsprechung.

Inhalte besser zugänglich machen

Mit Flutter sind Apps standardmäßig verfügbar. Beispielsweise werden in jeder Flutter-App alle Texte und interaktiven Elemente in der App korrekt für Screenreader wie TalkBack und VoiceOver angezeigt.

d1fad7944fb890ea.png

Manchmal ist jedoch etwas Arbeit erforderlich. Bei dieser App hat der Screenreader möglicherweise Probleme bei der Aussprache einiger generierter Wortpaare. Menschen haben kein Problem, die beiden Wörter in cheaphead zu identifizieren. Ein Screenreader sagt das ph in der Mitte des Wortes jedoch möglicherweise als f aus.

Eine einfache Lösung besteht darin, pair.asLowerCase durch "${pair.first} ${pair.second}" zu ersetzen. Letzterer verwendet Stringinterpolation, um aus den beiden Wörtern in pair einen String (z. B. "cheap head") zu erstellen. Die Verwendung von zwei separaten Wörtern anstelle eines zusammengesetzten Wortes stellt sicher, dass Screenreader diese korrekt identifizieren, und bietet sehbehinderten Nutzenden ein besseres Erlebnis.

Trotzdem solltest du die visuelle Einfachheit von pair.asLowerCase beibehalten. Mit der Eigenschaft semanticsLabel von Text kannst du den visuellen Inhalt des Text-Widgets mit einem semantischen Inhalt überschreiben, der für Screenreader besser geeignet ist:

lib/main.dart

// ...

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final style = theme.textTheme.displayMedium!.copyWith(
      color: theme.colorScheme.onPrimary,
    );

    return Card(
      color: theme.colorScheme.primary,
      child: Padding(
        padding: const EdgeInsets.all(20),

        // ↓ Make the following change.
        child: Text(
          pair.asLowerCase,
          style: style,
          semanticsLabel: "${pair.first} ${pair.second}",
        ),
      ),
    );
  }

// ...

Jetzt sprechen Screenreader jedes generierte Wortpaar richtig aus, die Benutzeroberfläche bleibt jedoch gleich. Probieren Sie diese Aktion aus, indem Sie einen Screenreader auf Ihrem Gerät verwenden.

Benutzeroberfläche zentrieren

Jetzt, da das zufällig ausgewählte Wortpaar optisch ansprechend ist, ist es an der Zeit, es in die Mitte des App-Fensters bzw. -Bildschirms zu platzieren.

Denken Sie zuerst daran, dass BigCard Teil einer Column ist. Standardmäßig werden die untergeordneten Elemente von Spalten ganz oben angezeigt. Dies lässt sich jedoch leicht überschreiben. Wechseln Sie zur build()-Methode von MyHomePage und nehmen Sie folgende Änderung vor:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,  // ← Add this.
        children: [
          Text('A random AWESOME idea:'),
          BigCard(pair: pair),
          ElevatedButton(
            onPressed: () {
              appState.getNext();
            },
            child: Text('Next'),
          ),
        ],
      ),
    );
  }
}

// ...

Dadurch werden die untergeordneten Elemente innerhalb von Column entlang der vertikalen Hauptachse zentriert.

b555d4c7f5000edf.png

Die untergeordneten Elemente sind bereits entlang der Kreuzachse der Spalte zentriert, d. h. sie sind bereits horizontal zentriert. Aber Column selbst ist nicht innerhalb der Scaffold zentriert. Das können wir mit dem Widget Inspector überprüfen.

Der Widget Inspector selbst wird in diesem Codelab nicht behandelt. Sie können jedoch sehen, dass das hervorgehobene Column-Objekt nicht die gesamte Breite der App einnimmt. Es nimmt nur so viel horizontalen Platz ein, wie seine Kinder benötigen.

Sie können einfach die Spalte selbst zentrieren. Bewegen Sie den Mauszeiger auf Column, rufen Sie das Menü Refaktorierung auf (mit Ctrl+. oder Cmd+.) und wählen Sie Mit Mitte umbrechen aus.

Die App sollte jetzt in etwa so aussehen:

455688d93c30d154.png

Wenn Sie möchten, können Sie dies weiter optimieren.

  • Du kannst das Text-Widget über BigCard entfernen. Es könnte argumentiert werden, dass der beschreibende Text („Eine zufällige Fantastische Idee:“) nicht mehr benötigt wird, da die Benutzeroberfläche auch ohne sie Sinn ergibt. So ist es auch übersichtlicher.
  • Du kannst auch ein SizedBox(height: 10)-Widget zwischen BigCard und ElevatedButton hinzufügen. Auf diese Weise sind die beiden Widgets etwas stärker voneinander getrennt. Das SizedBox-Widget nimmt nur Speicherplatz in Anspruch und rendert nichts selbst. Es wird häufig verwendet, um visuelle „Lücken“ zu schaffen.

Mit den optionalen Änderungen enthält MyHomePage diesen Code:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            BigCard(pair: pair),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: () {
                appState.getNext();
              },
              child: Text('Next'),
            ),
          ],
        ),
      ),
    );
  }
}

// ...

Die App sieht so aus:

3d53d2b071e2f372.png

Im nächsten Abschnitt fügen Sie die Möglichkeit hinzu, generierte Wörter als Favoriten zu markieren oder mit „Gefällt mir“ zu markieren.

6. Funktionen hinzufügen

Die App funktioniert und liefert gelegentlich sogar interessante Wortpaare. Wenn der Nutzer jedoch auf Weiter klickt, verschwindet jedes Wortpaar für immer. Es wäre besser, sich an eine Möglichkeit zu erinnern, wie eine positive Bewertung Schaltfläche.

e6b01a8c90df8ffa.png

Geschäftslogik hinzufügen

Scrollen Sie zu MyAppState und fügen Sie den folgenden Code hinzu:

lib/main.dart

// ...

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();

  void getNext() {
    current = WordPair.random();
    notifyListeners();
  }

  // ↓ Add the code below.
  var favorites = <WordPair>[];

  void toggleFavorite() {
    if (favorites.contains(current)) {
      favorites.remove(current);
    } else {
      favorites.add(current);
    }
    notifyListeners();
  }
}

// ...

Prüfen Sie die Änderungen:

  • Du hast MyAppState eine neue Property namens „favorites“ hinzugefügt. Dieses Attribut wird mit einer leeren Liste initialisiert: [].
  • Außerdem haben Sie mithilfe von generics angegeben, dass die Liste nur Wortpaare enthalten darf: <WordPair>[]. Dies trägt dazu bei, deine App robuster zu machen, denn Dart verweigert die Ausführung deiner App, wenn du versuchst, etwas anderes als WordPair hinzuzufügen. Sie können wiederum die favorites-Liste verwenden und wissen, dass sich nie unerwünschte Objekte wie null darin verstecken können.
  • Sie haben außerdem die neue Methode toggleFavorite() hinzugefügt, mit der das aktuelle Wortpaar entweder aus der Favoritenliste entfernt wird (sofern es bereits vorhanden ist) oder mit der es hinzugefügt wird (falls es noch nicht vorhanden ist). In beiden Fällen wird mit dem Code anschließend notifyListeners(); aufgerufen.

Schaltfläche hinzufügen

Mit der „Geschäftslogik“ ist es an der Zeit, wieder an der Benutzeroberfläche zu arbeiten. Platzieren der „Gefällt mir“-Angabe auf die Schaltfläche Schaltfläche erfordert Row. Das Row-Widget ist die horizontale Entsprechung zu Column, das Sie bereits gesehen haben.

Verpacke die vorhandene Schaltfläche zuerst in einem Row. Gehen Sie zur build()-Methode von MyHomePage, platzieren Sie den Mauszeiger auf ElevatedButton, rufen Sie mit Ctrl+. oder Cmd+. das Menü Refaktorieren auf und wählen Sie Mit Zeile umbrechen aus.

Beim Speichern werden Sie feststellen, dass sich Row ähnlich wie Column verhält. Standardmäßig werden die untergeordneten Elemente auf der linken Seite angezeigt. (Column hat seine untergeordneten Elemente ganz oben angeordnet.) Um dieses Problem zu beheben, können Sie denselben Ansatz wie zuvor verwenden, nur mit mainAxisAlignment. Verwende jedoch mainAxisSize fürdidaktische Zwecke (Lernen). Damit wird Row angewiesen, nicht den gesamten verfügbaren horizontalen Bereich zu belegen.

Nehmen Sie die folgende Änderung vor:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            BigCard(pair: pair),
            SizedBox(height: 10),
            Row(
              mainAxisSize: MainAxisSize.min,   // ← Add this.
              children: [
                ElevatedButton(
                  onPressed: () {
                    appState.getNext();
                  },
                  child: Text('Next'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// ...

Die Benutzeroberfläche wird wieder auf den ursprünglichen Zustand zurückgesetzt.

3d53d2b071e2f372.png

Fügen Sie als Nächstes die Schaltfläche Gefällt mir hinzu und verknüpfen Sie sie mit toggleFavorite(). Versuchen Sie es zuerst selbst, ohne sich den Codeblock unten anzusehen.

e6b01a8c90df8ffa.png

Es ist in Ordnung, wenn Sie es anders machen als unten. Um das Herzsymbol brauchst du dir sogar keine Gedanken zu machen, es sei denn, du möchtest dir wirklich eine große Herausforderung stellen.

Sie können auch scheitern. Das ist Ihre erste Stunde mit Flutter.

252f7c4a212c94d2.png

Hier ist eine Möglichkeit, die zweite Schaltfläche zu MyHomePage hinzuzufügen. Verwenden Sie dieses Mal den ElevatedButton.icon()-Konstruktor, um eine Schaltfläche mit einem Symbol zu erstellen. Oben in der Methode build wählen Sie das entsprechende Symbol aus, je nachdem, ob sich das aktuelle Wortpaar bereits in den Favoriten befindet. Beachte auch, dass du wieder SizedBox verwendest, damit die beiden Schaltflächen ein wenig voneinander entfernt sind.

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    // ↓ Add this.
    IconData icon;
    if (appState.favorites.contains(pair)) {
      icon = Icons.favorite;
    } else {
      icon = Icons.favorite_border;
    }

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            BigCard(pair: pair),
            SizedBox(height: 10),
            Row(
              mainAxisSize: MainAxisSize.min,
              children: [

                // ↓ And this.
                ElevatedButton.icon(
                  onPressed: () {
                    appState.toggleFavorite();
                  },
                  icon: Icon(icon),
                  label: Text('Like'),
                ),
                SizedBox(width: 10),

                ElevatedButton(
                  onPressed: () {
                    appState.getNext();
                  },
                  child: Text('Next'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// ...

Die App sollte so aussehen:

Der Nutzer kann die Favoriten nicht sehen. Als Nächstes fügen wir unserer App einen separaten Bildschirm hinzu. Bis zum nächsten Abschnitt!

7. Navigationsstreifen hinzufügen

Bei den meisten Apps passt nicht alles auf einen einzigen Bildschirm. Diese App könnte dies wahrscheinlich tun, aber aus informativen Gründen erstellen Sie einen separaten Bildschirm für die Favoriten der Nutzenden. Um zwischen den beiden Bildschirmen zu wechseln, implementierst du zuerst deine erste StatefulWidget.

f62c54f5401a187.png

Teile MyHomePage in zwei separate Widgets auf, um diesen Schritt so schnell wie möglich zu verarbeiten.

Wählen Sie alle Elemente in MyHomePage aus, löschen Sie sie und ersetzen Sie sie durch den folgenden Code:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          SafeArea(
            child: NavigationRail(
              extended: false,
              destinations: [
                NavigationRailDestination(
                  icon: Icon(Icons.home),
                  label: Text('Home'),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.favorite),
                  label: Text('Favorites'),
                ),
              ],
              selectedIndex: 0,
              onDestinationSelected: (value) {
                print('selected: $value');
              },
            ),
          ),
          Expanded(
            child: Container(
              color: Theme.of(context).colorScheme.primaryContainer,
              child: GeneratorPage(),
            ),
          ),
        ],
      ),
    );
  }
}


class GeneratorPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    IconData icon;
    if (appState.favorites.contains(pair)) {
      icon = Icons.favorite;
    } else {
      icon = Icons.favorite_border;
    }

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          BigCard(pair: pair),
          SizedBox(height: 10),
          Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              ElevatedButton.icon(
                onPressed: () {
                  appState.toggleFavorite();
                },
                icon: Icon(icon),
                label: Text('Like'),
              ),
              SizedBox(width: 10),
              ElevatedButton(
                onPressed: () {
                  appState.getNext();
                },
                child: Text('Next'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

// ...

Nach dem Speichern sehen Sie, dass die visuelle Seite der Benutzeroberfläche fertig ist, funktioniert aber nicht. Wenn du in der Navigationsleiste auf ♥︎ (das Herz) klickst, passiert nichts.

388bc25fe198c54a.png

Sehen Sie sich die Änderungen an.

  • Beachten Sie zuerst, dass der gesamte Inhalt von MyHomePage in ein neues Widget extrahiert wird: GeneratorPage. Der einzige Teil des alten MyHomePage-Widgets, der nicht extrahiert wurde, ist Scaffold.
  • Die neue MyHomePage enthält eine Row mit zwei untergeordneten Elementen. Das erste Widget ist SafeArea und das zweite ein Expanded-Widget.
  • Das SafeArea sorgt dafür, dass das untergeordnete Element nicht von einer Hardware-Notch oder einer Statusleiste verdeckt wird. In dieser App umschließt das Widget NavigationRail, damit beispielsweise die Navigationsschaltflächen nicht von einer mobilen Statusleiste verdeckt werden.
  • Sie können die Linie extended: false in NavigationRail in true ändern. Daraufhin werden die Labels neben den Symbolen angezeigt. In einem späteren Schritt erfahren Sie, wie dies automatisch geschieht, wenn die App genügend horizontalen Platz hat.
  • Die Navigationsstrecke hat zwei Ziele (Start und Favoriten) mit den entsprechenden Symbolen und Labels. Außerdem wird damit der aktuelle selectedIndex definiert. Ein ausgewählter Index von null wählt das erste Ziel aus, ein ausgewählter Index von Eins wählt das zweite Ziel aus und so weiter. Für den Moment ist er hartcodiert auf null.
  • In der Navigationsstrecke ist auch definiert, was passiert, wenn der Nutzer mit onDestinationSelected eines der Ziele auswählt. Derzeit gibt die Anwendung nur den angeforderten Indexwert mit print() aus.
  • Das zweite untergeordnete Element von Row ist das Expanded-Widget. Maximierte Widgets sind in Zeilen und Spalten äußerst nützlich. Damit können Sie Layouts ausdrücken, bei denen einige untergeordnete Elemente nur so viel Platz einnehmen, wie sie benötigen (in diesem Fall SafeArea). Andere Widgets sollten so viel Platz wie möglich einnehmen (in diesem Fall Expanded). Expanded-Widgets könnten zum Beispiel „gierig“ sein. Wenn du ein besseres Gefühl für die Rolle dieses Widgets bekommen möchtest, kannst du das SafeArea-Widget mit einem anderen Expanded-Element umschließen. Das resultierende Layout sieht in etwa so aus:

6bbda6c1835a1ae.png

  • Zwei Expanded-Widgets teilen den gesamten verfügbaren horizontalen Bereich unter sich auf, obwohl die Navigationsleiste nur ein kleines Stück links benötigt.
  • Im Expanded-Widget befindet sich eine farbige Container und im Container das GeneratorPage.

Zustandslose und zustandsorientierte Widgets im Vergleich

Bisher hat MyAppState alle deine Anforderungen in Bundesstaaten abgedeckt. Aus diesem Grund sind alle bislang erstellten Widgets zustandslos. Sie enthalten keine eigenen veränderlichen Status. Keines der Widgets kann sich selbst ändern. Sie müssen MyAppState durchlaufen.

Dies wird sich bald ändern.

Der Wert für selectedIndex der Navigationsstrecke muss in irgendeiner Weise festgehalten werden. Du möchtest diesen Wert auch innerhalb des onDestinationSelected-Callbacks ändern können.

Du könntest selectedIndex als weitere Property von MyAppState hinzufügen. Und es würde funktionieren. Sie können sich jedoch vorstellen, dass der App-Status schnell über die Vernunft hinauswachsen würde, wenn jedes Widget seine Werte darin speichern würde.

e52d9c0937cc0823.jpeg

Ein Status ist nur für ein einzelnes Widget relevant, daher sollte er bei diesem Widget bleiben.

Geben Sie StatefulWidget ein, eine Art von Widget, das State enthält. Konvertieren Sie zuerst MyHomePage in ein zustandsorientiertes Widget.

Platzieren Sie den Cursor in der ersten Zeile von MyHomePage (die mit class MyHomePage... beginnt) und rufen Sie mit Ctrl+. oder Cmd+. das Menü Refaktorierung auf. Wählen Sie dann Convert to StatefulWidget aus.

Die IDE erstellt eine neue Klasse für Sie: _MyHomePageState. Diese Klasse erweitert State und kann daher ihre eigenen Werte verwalten. (Kann sich selbst ändern.) Beachten Sie auch, dass die Methode build aus dem alten, zustandslosen Widget in _MyHomePageState verschoben wurde (anstatt im Widget zu bleiben). Sie wurde wörtlich verschoben. In der Methode build hat sich nichts geändert. Jetzt lebt es nur noch woanders.

setState

Das neue zustandsorientierte Widget muss nur eine Variable erfassen: selectedIndex. Nehmen Sie die folgenden drei Änderungen an _MyHomePageState vor:

lib/main.dart

// ...

class _MyHomePageState extends State<MyHomePage> {

  var selectedIndex = 0;     // ← Add this property.

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          SafeArea(
            child: NavigationRail(
              extended: false,
              destinations: [
                NavigationRailDestination(
                  icon: Icon(Icons.home),
                  label: Text('Home'),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.favorite),
                  label: Text('Favorites'),
                ),
              ],
              selectedIndex: selectedIndex,    // ← Change to this.
              onDestinationSelected: (value) {

                // ↓ Replace print with this.
                setState(() {
                  selectedIndex = value;
                });

              },
            ),
          ),
          Expanded(
            child: Container(
              color: Theme.of(context).colorScheme.primaryContainer,
              child: GeneratorPage(),
            ),
          ),
        ],
      ),
    );
  }
}

// ...

Prüfen Sie die Änderungen:

  1. Sie fügen die neue Variable selectedIndex ein und initialisieren sie mit 0.
  2. Sie verwenden diese neue Variable in der NavigationRail-Definition anstelle der bisher hartcodierten 0.
  3. Wenn der onDestinationSelected-Callback aufgerufen wird, weisen Sie ihn selectedIndex innerhalb eines setState()-Aufrufs zu, anstatt ihn nur in der Konsole auszugeben. Dieser Aufruf ähnelt der zuvor verwendeten Methode notifyListeners(). Damit wird sichergestellt, dass die Benutzeroberfläche aktualisiert wird.

Die Navigationsschiene reagiert jetzt auf Nutzerinteraktionen. Der erweiterte Bereich auf der rechten Seite bleibt jedoch gleich. Das liegt daran, dass der Code nicht selectedIndex verwendet, um zu bestimmen, welcher Bildschirm angezeigt wird.

selectedIndex verwenden

Platzieren Sie den folgenden Code am Anfang der build-Methode von _MyHomePageState direkt vor return Scaffold:

lib/main.dart

// ...

Widget page;
switch (selectedIndex) {
  case 0:
    page = GeneratorPage();
    break;
  case 1:
    page = Placeholder();
    break;
  default:
    throw UnimplementedError('no widget for $selectedIndex');
}

// ...

Untersuchen Sie dieses Code-Snippet:

  1. Der Code deklariert die neue Variable page vom Typ Widget.
  2. Dann weist eine Switch-Anweisung page einen Bildschirm gemäß dem aktuellen Wert in selectedIndex zu.
  3. Da noch kein FavoritesPage vorhanden ist, verwende Placeholder. ein praktisches Widget, das ein gekreuztes Rechteck zeichnet, wo immer Sie es platzieren, und diesen Teil der Benutzeroberfläche als unfertig markiert.

5685cf886047f6ec.png

  1. Entsprechend dem Fail-Fast-Prinzip sorgt die Switch-Anweisung auch dafür, dass ein Fehler ausgegeben wird, wenn selectedIndex weder 0 noch 1 ist. So lassen sich spätere Fehler vermeiden. Wenn Sie jemals ein neues Ziel zum Navigationspfad hinzufügen und vergessen, diesen Code zu aktualisieren, stürzt das Programm während der Entwicklung ab (im Gegensatz dazu, dass Sie erraten können, warum etwas nicht funktioniert, oder Sie können einen fehlerhaften Code in die Produktion veröffentlichen).

Da page jetzt das Widget enthält, das auf der rechten Seite angezeigt werden soll, können Sie wahrscheinlich raten, welche weiteren Änderungen erforderlich sind.

Hier ist _MyHomePageState nach der letzten verbleibenden Änderung:

lib/main.dart

// ...

class _MyHomePageState extends State<MyHomePage> {
  var selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    Widget page;
    switch (selectedIndex) {
      case 0:
        page = GeneratorPage();
        break;
      case 1:
        page = Placeholder();
        break;
      default:
        throw UnimplementedError('no widget for $selectedIndex');
    }

    return Scaffold(
      body: Row(
        children: [
          SafeArea(
            child: NavigationRail(
              extended: false,
              destinations: [
                NavigationRailDestination(
                  icon: Icon(Icons.home),
                  label: Text('Home'),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.favorite),
                  label: Text('Favorites'),
                ),
              ],
              selectedIndex: selectedIndex,
              onDestinationSelected: (value) {
                setState(() {
                  selectedIndex = value;
                });
              },
            ),
          ),
          Expanded(
            child: Container(
              color: Theme.of(context).colorScheme.primaryContainer,
              child: page,  // ← Here.
            ),
          ),
        ],
      ),
    );
  }
}


// ...

Die App wechselt jetzt zwischen GeneratorPage und dem Platzhalter, der bald zur Seite Favoriten wird.

Ansprechbarkeit

Machen Sie als Nächstes die Navigationsschiene responsiv. Das heißt, die Labels werden automatisch angezeigt (mithilfe von extended: true), wenn genügend Platz für sie vorhanden ist.

a8873894c32e0d0b.png

Flutter bietet verschiedene Widgets, mit denen du dafür sorgen kannst, dass deine Apps automatisch reagieren. Zum Beispiel ist Wrap ein Widget ähnlich Row oder Column, das untergeordnete Elemente automatisch bis zur nächsten „Zeile“ einschließt. („Laufen“), wenn nicht genügend vertikaler oder horizontaler Platz vorhanden ist. Hier gibt es FittedBox. Dabei handelt es sich um ein Widget, bei dem das untergeordnete Element automatisch entsprechend Ihren Angaben in den verfügbaren Bereich passt.

NavigationRail zeigt Labels jedoch nicht automatisch an, wenn genügend Platz vorhanden ist, da nicht in jedem Kontext herausgefunden werden kann, wie viel Platz vorhanden ist. Die Entscheidung liegt bei Ihnen, dem Entwickler.

Angenommen, Labels werden nur angezeigt, wenn MyHomePage mindestens 600 Pixel breit ist.

Das zu verwendende Widget ist in diesem Fall LayoutBuilder. Damit können Sie Ihre Widget-Struktur abhängig vom verfügbaren Speicherplatz ändern.

Nehmen Sie die erforderlichen Änderungen in VS Code über das Flutter-Menü Refaktorieren wieder vor. Dieses Mal ist es jedoch etwas komplizierter:

  1. Bewegen Sie den Cursor in der Methode build von _MyHomePageState auf Scaffold.
  2. Rufen Sie das Menü Refaktorierung mit Ctrl+. (Windows/Linux) oder Cmd+. (Mac) auf.
  3. Wählen Sie Mit Builder umschließen aus und drücken Sie die Eingabetaste.
  4. Ändern Sie den Namen der neu hinzugefügten Builder zu LayoutBuilder.
  5. Ändere die Liste der Callback-Parameter von (context) zu (context, constraints).

Der builder-Callback von LayoutBuilder wird jedes Mal aufgerufen, wenn sich die Einschränkungen ändern. Das passiert z. B. in folgenden Fällen:

  • Der Nutzer ändert die Größe des App-Fensters.
  • Der Nutzer dreht sein Smartphone vom Hochformat ins Querformat oder zurück.
  • Ein Widget neben MyHomePage wird größer, wodurch die Einschränkungen von MyHomePage verringert werden
  • usw.

Jetzt kann Ihr Code entscheiden, ob das Label angezeigt werden soll, indem der aktuelle constraints abgefragt wird. Nehmen Sie die folgende einzeilige Änderung an der Methode build von _MyHomePageState vor:

lib/main.dart

// ...

class _MyHomePageState extends State<MyHomePage> {
  var selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    Widget page;
    switch (selectedIndex) {
      case 0:
        page = GeneratorPage();
        break;
      case 1:
        page = Placeholder();
        break;
      default:
        throw UnimplementedError('no widget for $selectedIndex');
    }

    return LayoutBuilder(builder: (context, constraints) {
      return Scaffold(
        body: Row(
          children: [
            SafeArea(
              child: NavigationRail(
                extended: constraints.maxWidth >= 600,  // ← Here.
                destinations: [
                  NavigationRailDestination(
                    icon: Icon(Icons.home),
                    label: Text('Home'),
                  ),
                  NavigationRailDestination(
                    icon: Icon(Icons.favorite),
                    label: Text('Favorites'),
                  ),
                ],
                selectedIndex: selectedIndex,
                onDestinationSelected: (value) {
                  setState(() {
                    selectedIndex = value;
                  });
                },
              ),
            ),
            Expanded(
              child: Container(
                color: Theme.of(context).colorScheme.primaryContainer,
                child: page,
              ),
            ),
          ],
        ),
      );
    });
  }
}


// ...

Jetzt reagiert Ihre App auf ihre Umgebung, wie z. B. Bildschirmgröße, Ausrichtung und Plattform. Mit anderen Worten, sie ist responsiv!

Sie müssen lediglich den Placeholder-Bildschirm durch einen tatsächlichen Favoriten-Bildschirm ersetzen. Das wird im nächsten Abschnitt behandelt.

8. Neue Seite hinzufügen

Erinnern Sie sich an das Widget Placeholder, das wir anstelle der Seite Favoriten verwendet haben?

Es ist Zeit, dieses Problem zu beheben.

Wenn du experimentierfreudig bist, versuche diesen Schritt alleine zu machen. Ihr Ziel ist es, die Liste der favorites im neuen zustandslosen Widget FavoritesPage anzuzeigen und dann dieses Widget anstelle von Placeholder anzuzeigen.

Hier einige Tipps:

  • Wenn ein Column scrollen soll, verwenden Sie das ListView-Widget.
  • Denken Sie daran, mit context.watch<MyAppState>() von einem beliebigen Widget aus auf die Instanz MyAppState zuzugreifen.
  • Wenn du auch ein neues Widget ausprobieren möchtest, findest du in ListTile Eigenschaften wie title (allgemein für Text), leading (für Symbole oder Avatare) und onTap (für Interaktionen). Mit bereits bekannten Widgets können Sie jedoch ähnliche Effekte erzielen.
  • Dart ermöglicht die Verwendung von for-Schleifen innerhalb von Sammlungsliteralen. Wenn messages beispielsweise eine Liste mit Strings enthält, können Sie Code wie den folgenden verwenden:

f0444bba08f205aa.png

Wenn Sie dagegen mehr Erfahrung mit der funktionalen Programmierung haben, können Sie in Dart auch Code wie messages.map((m) => Text(m)).toList() schreiben. Natürlich können Sie auch jederzeit eine Liste mit Widgets erstellen und diese unbedingt in der Methode build hinzufügen.

Wenn Sie die Seite Favoriten selbst hinzufügen, können Sie durch eigene Entscheidungen mehr darüber erfahren. Der Nachteil ist, dass Sie möglicherweise auf Probleme stoßen, die Sie noch nicht alleine lösen können. Denken Sie daran: Scheitern ist in Ordnung und eines der wichtigsten Elemente beim Lernen. Niemand erwartet, dass Sie die Flutter-Entwicklung in der ersten Stunde erfolgreich umsetzen – und das auch.

252f7c4a212c94d2.png

Im Folgenden findest du nur eine Möglichkeit, die Favoritenseite zu implementieren. Die Art und Weise der Implementierung wird Sie (hoffentlich) dazu inspirieren, mit dem Code zu experimentieren. Verbessern Sie die Benutzeroberfläche und gestalten Sie sie an Ihre Anforderungen.

Hier ist die neue FavoritesPage-Klasse:

lib/main.dart

// ...

class FavoritesPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();

    if (appState.favorites.isEmpty) {
      return Center(
        child: Text('No favorites yet.'),
      );
    }

    return ListView(
      children: [
        Padding(
          padding: const EdgeInsets.all(20),
          child: Text('You have '
              '${appState.favorites.length} favorites:'),
        ),
        for (var pair in appState.favorites)
          ListTile(
            leading: Icon(Icons.favorite),
            title: Text(pair.asLowerCase),
          ),
      ],
    );
  }
}

So funktioniert das Widget:

  • Sie ruft den aktuellen Status der App ab.
  • Wenn die Favoritenliste leer ist, wird in der Mitte die folgende Nachricht angezeigt: Noch keine Favoriten*.*
  • Andernfalls wird eine scrollbare Liste angezeigt.
  • Die Liste beginnt mit einer Zusammenfassung, z. B. Du hast 5 Favoriten*.*
  • Der Code durchläuft dann alle Favoriten und erstellt für jeden einzelnen ein ListTile-Widget.

Jetzt müssen Sie nur noch das Placeholder-Widget durch ein FavoritesPage ersetzen. Und voilà!

Sie können den endgültigen Code dieser App im Codelab-Repository auf GitHub abrufen.

9. Nächste Schritte

Glückwunsch!

Sieh mal einer an! Sie haben ein nicht funktionales Gerüst mit einem Column- und zwei Text-Widgets in eine responsive, kleine App umgewandelt.

d6e3d5f736411f13.png

Behandelte Themen

  • Grundlagen der Funktionsweise von Flutter
  • Layouts in Flutter erstellen
  • Verbindung zwischen Nutzerinteraktionen (z. B. Drücken von Tasten) und App-Verhalten herstellen
  • Flutter-Code organisieren
  • App responsiv machen
  • Ein einheitliches Erscheinungsbild die Funktionsweise Ihrer App

Nächster Schritt

  • Experimentieren Sie mit der App, die Sie in diesem Lab geschrieben haben.
  • Sehen Sie sich den Code dieser erweiterten Version derselben App an. Dort erfahren Sie, wie Sie unter anderem animierte Listen, Farbverläufe und Überblendungen hinzufügen können.