1. Einführung
Flutter ist das UI-Toolkit von Google zum Erstellen von Anwendungen für Mobilgeräte, das Web und Computer auf einer gemeinsamen Codebasis. In diesem Codelab erstellen Sie die folgende Flutter-Anwendung:
Die Anwendung generiert klingende Namen wie „newstay“, „lightstream“, „mainbrake“ oder „graypine“. Der Nutzer kann den nächsten Namen anfordern, den aktuellen Namen als Favoriten markieren und die Liste der Favoritennamen auf einer separaten Seite aufrufen. Die App ist für verschiedene Bildschirmgrößen optimiert.
Lerninhalte
- Grundlagen der Funktionsweise von Flutter
- Layouts in Flutter erstellen
- Nutzerinteraktionen (z. B. Tastendrücke) mit dem App-Verhalten verknüpfen
- Flutter-Code organisieren
- App responsiv gestalten (für verschiedene Bildschirme)
- Einheitliches Erscheinungsbild Ihrer App
Sie beginnen mit einem einfachen Gerüst, damit Sie direkt zu den interessanten Teilen springen können.
Und 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
Damit dieses Codelab so einfach wie möglich ist, gehen wir davon aus, dass Sie Visual Studio Code (VS Code) als Entwicklungsumgebung verwenden. Die App ist kostenlos und funktioniert auf allen wichtigen Plattformen.
Sie können natürlich einen beliebigen Editor verwenden: Android Studio, andere IntelliJ-IDEs, Emacs, Vim oder Notepad++. Sie alle funktionieren mit Flutter.
Wir empfehlen, für dieses Codelab VS Code zu verwenden, da in der Anleitung standardmäßig VS Code-spezifische Tastenkombinationen verwendet werden. Es ist einfacher, Dinge wie „Klicken Sie hier“ oder „Drücken Sie diese Taste“ zu sagen, anstatt „Führen Sie die entsprechende Aktion in Ihrem Editor aus, um X zu tun“.
Entwicklungsziel auswählen
Flutter ist ein plattformübergreifendes Toolkit. Ihre App kann auf einem der folgenden Betriebssysteme ausgeführt werden:
- iOS
- Android
- Windows
- macOS
- Linux
- web
Es ist jedoch üblich, ein einzelnes Betriebssystem auszuwählen, für das Sie hauptsächlich entwickeln. Das ist dann Ihr Entwicklungsziel – das Betriebssystem, auf dem Ihre Anwendung während der Entwicklungsphase ausgeführt wird.
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 dann 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 neben dem Editor ausgeführt.
Es kann verlockend sein, das Web als Entwicklungsziel auszuwählen. Der Nachteil dieser Option ist, dass Sie eine der nützlichsten Entwicklungsfunktionen von Flutter verlieren: Stateful Hot Reload. Flutter kann Webanwendungen nicht per Hot Reload neu laden.
Treffen Sie jetzt Ihre Auswahl. Hinweis: Sie können Ihre App später jederzeit auf anderen Betriebssystemen ausführen. Mit einem klaren Entwicklungsziel vor Augen lässt sich der nächste Schritt leichter umsetzen.
Flutter installieren
Die aktuellste Anleitung zur Installation des Flutter SDK finden Sie immer unter docs.flutter.dev.
Die Anleitung auf der Flutter-Website umfasst nicht nur die Installation des SDK selbst, sondern auch die Entwicklungstools und die Editor-Plug-ins. Denken Sie daran, dass Sie für dieses Codelab nur Folgendes installieren müssen:
- Flutter SDK
- Visual Studio Code mit dem Flutter-Plug-in
- Die Software, die für das von Ihnen gewählte Entwicklungsziel erforderlich ist (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, finden Sie möglicherweise in einigen dieser Fragen und Antworten (von Stack Overflow) hilfreiche Informationen zur Fehlerbehebung.
FAQ
- Wie finde ich den Pfad des Flutter SDK?
- Was kann ich tun, wenn der Flutter-Befehl nicht gefunden wird?
- Wie behebe ich das Problem „Waiting for another flutter command to release the startup lock“?
- Wie teile ich Flutter mit, wo sich meine Android SDK-Installation befindet?
- Wie gehe ich mit dem Java-Fehler um, der beim Ausführen von
flutter doctor --android-licenses
auftritt? - Wie gehe ich vor, wenn das Android-Tool
sdkmanager
nicht gefunden wird? - Wie gehe ich mit dem Fehler „Die Komponente
cmdline-tools
fehlt“ um? - Wie führe ich CocoaPods auf Apple Silicon (M1) aus?
- Wie deaktiviere ich die automatische Formatierung beim Speichern in VS Code?
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
). Beginnen Sie mit der Eingabe von „flutter new“. Wählen Sie den Befehl Flutter: New Project aus.
Wählen Sie als Nächstes Application (Anwendung) und dann einen Ordner aus, in dem Sie das Projekt erstellen möchten. Das kann Ihr Basisverzeichnis oder ein Verzeichnis wie C:\src\
sein.
Geben Sie Ihrem Projekt einen Namen. Beispiel: namer_app
oder my_awesome_namer
.
Flutter erstellt nun den Projektordner und VS Code öffnet ihn.
Sie überschreiben jetzt den Inhalt von drei Dateien mit einem einfachen Gerüst der App.
Kopieren und Einfügen der ursprünglichen App
Achten Sie darauf, dass im linken Bereich von VS Code Explorer ausgewählt ist, und öffnen Sie die Datei pubspec.yaml
.
Ersetzen Sie den Inhalt dieser Datei durch Folgendes:
pubspec.yaml
name: namer_app
description: "A new Flutter project."
publish_to: "none"
version: 0.1.0
environment:
sdk: ^3.9.0
dependencies:
flutter:
sdk: flutter
english_words: ^4.0.0
provider: ^6.1.5
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
flutter:
uses-material-design: true
In der Datei pubspec.yaml
werden grundlegende Informationen zu Ihrer App angegeben, z. B. die aktuelle Version, die Abhängigkeiten und die Assets, die mit der App ausgeliefert werden.
Öffnen Sie als Nächstes eine andere Konfigurationsdatei im Projekt, analysis_options.yaml
.
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
Diese Datei bestimmt, wie streng Flutter bei der Analyse Ihres Codes sein soll. Da dies Ihr erster Ausflug in Flutter ist, weisen Sie den Analyzer an, es langsam angehen zu lassen. Sie können diese Einstellung später jederzeit anpassen. Wenn Sie eine Produktions-App veröffentlichen möchten, sollten Sie den Analyzer sogar noch strenger einstellen.
Öffnen Sie schließlich die Datei main.dart
im Verzeichnis lib/
.
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(
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 Zeilen Code sind die gesamte App bis jetzt.
Im nächsten Abschnitt führen Sie die Anwendung im Debug-Modus aus und beginnen mit der Entwicklung.
4. Schaltfläche hinzufügen
In diesem Schritt wird die Schaltfläche Weiter hinzugefügt, mit der ein neues Wortpaar generiert werden kann.
App starten
Öffnen Sie zuerst lib/main.dart
und prüfen Sie, ob Ihr Zielgerät ausgewählt ist. Rechts unten in VS Code sehen Sie eine Schaltfläche mit dem aktuellen Zielgerät. Klicken Sie darauf, um sie zu ändern.
Suchen Sie in lib/main.dart
oben rechts im Fenster von VS Code nach der Schaltfläche „Wiedergabe“ und klicken Sie darauf.
Nach etwa einer Minute wird Ihre App im Debug-Modus gestartet. Bisher ist noch nicht viel zu sehen:
Erster Hot Reload
Fügen Sie unten in lib/main.dart
dem String im ersten Text
-Objekt etwas hinzu und speichern Sie die Datei (mit Ctrl+S
oder Cmd+S
). Beispiel:
lib/main.dart
// ...
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'), // ← Example change.
Text(appState.current.asLowerCase),
],
),
);
// ...
Beachten Sie, dass sich die App sofort ändert, das zufällige Wort jedoch gleich bleibt. Das ist der berühmte zustandsorientierte Hot Reload von Flutter. Hot Reload wird ausgelöst, wenn Sie Änderungen an einer Quelldatei speichern.
Häufig gestellte Fragen
- Was ist, wenn Hot Reload in VSCode nicht funktioniert?
- Muss ich in VSCode „r“ für Hot Reload drücken?
- Funktioniert Hot Reload im Web?
- Wie entferne ich das Banner „Debug“?
Schaltfläche hinzufügen
Fügen Sie als Nächstes unten im Column
, direkt unter der zweiten Text
-Instanz, eine Schaltfläche 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 noch einmal aktualisiert: Es wird eine Schaltfläche angezeigt. Wenn Sie darauf klicken, wird in der Debug Console in VS Code die Meldung button pressed! angezeigt.
Flutter-Crashkurs in 5 Minuten
Die Debug Console ist zwar ganz interessant, aber Sie möchten, dass die Schaltfläche etwas Sinnvolleres bewirkt. Sehen Sie sich aber zuerst den Code in lib/main.dart
an, 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(
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.
Der Code in MyApp
richtet die gesamte App ein. Er erstellt den appweiten Status (mehr dazu später), benennt die App, definiert das visuelle Design und legt das „Home“-Widget fest, den Ausgangspunkt Ihrer App.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
// ...
Als Nächstes wird in der Klasse MyAppState
der Status der App definiert. Dies ist Ihr erster Ausflug in Flutter. Daher ist dieses Codelab einfach und fokussiert gehalten. Es gibt viele leistungsstarke Möglichkeiten, den App-Status in Flutter zu verwalten. Einer der einfachsten Ansätze ist ChangeNotifier
, der von dieser App verwendet wird.
MyAppState
definiert die Daten, die für die Funktion der App erforderlich sind. Derzeit enthält sie nur eine Variable mit dem aktuellen zufälligen Wortpaar. Sie fügen später weitere hinzu.- Die Statusklasse erweitert
ChangeNotifier
. Das 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 mit einem
ChangeNotifierProvider
erstellt und für die gesamte App bereitgestellt (siehe Code oben inMyApp
). So kann jedes Widget in der App auf den Status zugreifen.
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 Zeilennummer-Kommentar im Code oben:
- 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. MyHomePage
verfolgt Änderungen am aktuellen Status der App mithilfe der Methodewatch
.- Jede
build
-Methode muss ein Widget oder (in der Regel) einen verschachtelten Baum von Widgets zurückgeben. In diesem Fall ist das Widget der obersten EbeneScaffold
. In diesem Codelab arbeiten Sie nicht mitScaffold
, aber es ist ein nützliches Widget, das in den meisten Flutter-Apps in der Praxis verwendet wird. Column
ist eines der grundlegendsten Layout-Widgets in Flutter. Es werden beliebig viele untergeordnete Elemente in einer Spalte von oben nach unten angeordnet. Standardmäßig werden die untergeordneten Elemente der Spalte oben platziert. Das werden Sie aber gleich ändern, damit die Spalte zentriert wird.- Sie haben dieses
Text
-Widget im ersten Schritt geändert. - Dieses zweite
Text
-Widget verwendetappState
und greift auf das einzige Element dieser Klasse zu,current
(einWordPair
).WordPair
bietet mehrere hilfreiche Getter wieasPascalCase
oderasSnakeCase
. Hier verwenden wirasLowerCase
. Sie können dies jedoch jetzt ändern, wenn Sie eine der Alternativen bevorzugen. - Beachten Sie, dass in Flutter-Code häufig nachgestellte Kommas verwendet werden. Dieses Komma ist nicht erforderlich, da
children
das letzte (und auch einzige) Element dieserColumn
-Parameterliste ist. Es ist jedoch im Allgemeinen eine gute Idee, nachgestellte Kommas zu verwenden: Sie erleichtern das Hinzufügen weiterer Elemente und dienen auch als Hinweis für den automatischen Formatierer von Dart, dort eine neue Zeile einzufügen. Weitere Informationen finden Sie unter Code formatieren.
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();
}
}
// ...
Mit der neuen Methode getNext()
wird current
mit einer neuen zufälligen WordPair
neu zugewiesen. Außerdem wird notifyListeners()
aufgerufen, eine Methode von ChangeNotifier)
, die dafür sorgt, dass alle, die MyAppState
beobachten, benachrichtigt werden.
Jetzt müssen Sie nur noch die Methode getNext
über den Callback der Schaltfläche aufrufen.
lib/main.dart
// ...
ElevatedButton(
onPressed: () {
appState.getNext(); // ← This instead of print().
},
child: Text('Next'),
),
// ...
Speichern Sie die App und probieren Sie sie jetzt aus. Bei jedem Drücken der Schaltfläche Weiter sollte ein neues zufälliges Wortpaar generiert werden.
Im nächsten Abschnitt gestalten Sie die Benutzeroberfläche ansprechender.
5. App ansprechender gestalten
So sieht die App derzeit aus.
Nicht gut. Das Herzstück der App, das zufällig generierte Wortpaar, sollte besser sichtbar sein. Das ist schließlich der Hauptgrund, warum unsere Nutzer diese App verwenden. Außerdem sind die App-Inhalte seltsam zentriert und die gesamte App ist langweilig schwarz-weiß.
In diesem Abschnitt werden diese Probleme durch die Überarbeitung des App-Designs behoben. Das Endziel für diesen Abschnitt ist etwa Folgendes:
Widget extrahieren
Die Zeile, die für die Anzeige des aktuellen Wortpaars verantwortlich ist, sieht jetzt so aus: Text(appState.current.asLowerCase)
. Wenn Sie sie in etwas Komplexeres ändern möchten, ist es eine gute Idee, diese Zeile in ein separates Widget zu extrahieren. Separate Widgets für separate logische Teile der Benutzeroberfläche sind eine wichtige Möglichkeit, die Komplexität in Flutter zu verringern.
Flutter bietet eine Refactoring-Hilfe zum Extrahieren von Widgets. Bevor Sie sie verwenden, sollten Sie jedoch darauf achten, dass die extrahierte Zeile nur auf das zugreift, was sie benötigt. Derzeit greift die Zeile auf appState
zu, muss aber eigentlich nur wissen, welches Wortpaar gerade verwendet wird.
Schreiben Sie das MyHomePage
-Widget daher 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 nun das Menü Refactor auf. In VS Code haben Sie dafür zwei Möglichkeiten:
- Klicken Sie mit der rechten Maustaste auf den Code, den Sie umgestalten möchten (in diesem Fall
Text
), und wählen Sie im Drop-down-Menü Umgestalten… aus.
ODER
- Bewegen Sie den Cursor auf den Codeabschnitt, den Sie umgestalten möchten (in diesem Fall
Text
), und drücken SieCtrl+.
(Windows/Linux) oderCmd+.
(Mac).
Wählen Sie im Menü Refactor (Umgestalten) die Option Extract Widget (Widget extrahieren) aus. Weisen Sie einen Namen wie BigCard zu und klicken Sie auf Enter
.
Dadurch wird automatisch eine neue Klasse, BigCard
, am Ende der aktuellen Datei erstellt. Die Klasse 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 dieses Refactorings weiter funktioniert.
Hinzufügen einer Karte
Jetzt ist es an der Zeit, dieses neue Widget in das auffällige UI-Element zu verwandeln, das wir uns am Anfang dieses Abschnitts vorgestellt haben.
Suchen Sie die Klasse BigCard
und die Methode build()
darin. Rufen Sie wie bisher das Menü Refactor (Umgestalten) im Text
-Widget auf. Diesmal extrahieren Sie das Widget jedoch nicht.
Wählen Sie stattdessen Mit Padding umbrechen aus. Dadurch wird ein neues übergeordnetes Widget namens Padding
um das Widget Text
erstellt. Nach dem Speichern hat das zufällige Wort mehr Platz.
Erhöhen Sie den Abstand vom Standardwert 8.0
. Verwenden Sie beispielsweise 20
für einen größeren Abstand.
Gehen Sie als Nächstes eine Ebene höher. Bewegen Sie den Mauszeiger auf das Padding
-Widget, rufen Sie das Menü Refactor auf und wählen Sie Wrap with widget... aus.
So können Sie das übergeordnete Widget angeben. Geben Sie „Card“ ein und drücken Sie die Eingabetaste.
Dadurch wird das Padding
-Widget und damit auch das Text
-Widget in ein Card
-Widget eingeschlossen.
lib/main.dart
// ...
class BigCard extends StatelessWidget {
const BigCard({super.key, required this.pair});
final WordPair pair;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Text(pair.asLowerCase),
),
);
}
}
// ...
Die App sieht nun in etwa so aus:
Design und Stil
Um die Karte besser hervorzuheben, können Sie sie mit einer kräftigeren Farbe bemalen. Da es immer eine gute Idee ist, ein einheitliches Farbschema zu verwenden, können Sie die Farbe über das Theme
der App auswählen.
Nehmen Sie die folgenden Änderungen an der build()
-Methode 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 erledigen viel Arbeit:
- Zuerst wird mit
Theme.of(context)
das aktuelle Design der App angefordert. - Anschließend wird die Farbe der Karte auf dieselbe Farbe wie die
colorScheme
-Eigenschaft des Designs festgelegt. Das Farbschema enthält viele Farben undprimary
ist die wichtigste Farbe der App.
Die Karte wird jetzt in der Primärfarbe der App dargestellt:
Sie können diese Farbe und das Farbschema der gesamten App ändern, indem Sie nach oben zu MyApp
scrollen und dort die Ausgangsfarbe für ColorScheme
ändern.
Die Farbe wird flüssig animiert. Das wird als implizite Animation bezeichnet. Viele Flutter-Widgets interpolieren Werte fließend, sodass die Benutzeroberfläche nicht einfach zwischen Zuständen „springt“.
Die Farbe der erhöhten Schaltfläche unter der Karte ändert sich ebenfalls. Das ist der Vorteil der Verwendung eines app-weiten Theme
im Gegensatz zur Hartcodierung von Werten.
TextTheme
Die Karte hat immer noch ein Problem: Der Text ist zu klein und seine Farbe ist schwer zu lesen. Nehmen Sie dazu die folgenden Änderungen an der build()
-Methode von BigCard
vor.
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),
),
);
}
// ...
Hintergrund dieser Änderung:
- Mit
theme.textTheme,
greifen Sie auf das Schriftartdesign der App zu. Diese Klasse umfasst Elemente wiebodyMedium
(für Standardtext mittlerer Größe),caption
(für Bildunterschriften) oderheadlineLarge
(für große Überschriften). - Das Attribut
displayMedium
ist ein großer Stil, der für Anzeigetext vorgesehen ist. Das Wort display wird hier im typografischen Sinne verwendet, z. B. in display typeface (Schriftart für Überschriften). In der Dokumentation fürdisplayMedium
heißt es, dass „Anzeigestile für kurzen, wichtigen Text reserviert sind“ – genau unser Anwendungsfall. - Die
displayMedium
-Eigenschaft des Designs könnte theoretischnull
sein. Dart, die Programmiersprache, in der Sie diese App schreiben, ist null-safe. Sie können also keine Methoden von Objekten aufrufen, die möglicherweisenull
sind. In diesem Fall können Sie jedoch den!
-Operator („Bang-Operator“) verwenden, um Dart zu versichern, dass Sie wissen, was Sie tun.displayMedium
ist in diesem Fall definitiv nicht null. Warum das so ist, wird in diesem Codelab nicht behandelt. - Wenn Sie
copyWith()
fürdisplayMedium
aufrufen, wird eine Kopie des Textstils mit den von Ihnen definierten Änderungen zurückgegeben. In diesem Fall ändern Sie nur die Farbe des Textes. - Um die neue Farbe zu erhalten, greifen Sie noch einmal auf das Design der App zu. Die
onPrimary
-Property des Farbschemas definiert eine Farbe, die sich gut für die Verwendung auf der primären Farbe der App eignet.
Die App sollte jetzt in etwa so aussehen:
Wenn Sie möchten, können Sie die Karte weiter bearbeiten. Hier einige Tipps:
- Mit
copyWith()
können Sie viel mehr als nur die Farbe des Textes ändern. Wenn Sie die vollständige Liste der Eigenschaften aufrufen möchten, die Sie ändern können, setzen Sie den Cursor an eine beliebige Stelle in die Klammern voncopyWith()
und drücken SieCtrl+Shift+Space
(Windows/Linux) oderCmd+Shift+Space
(Mac). - Ebenso können Sie weitere Einstellungen für das
Card
-Widget ändern. Sie können beispielsweise den Schatten der Karte vergrößern, indem Sie den Wert des Parameterselevation
erhöhen. - Experimentieren Sie mit Farben. Neben
theme.colorScheme.primary
gibt es auch.secondary
,.surface
und viele andere. Für alle diese Farben gibt esonPrimary
-Entsprechungen.
Inhalte besser zugänglich machen
Flutter macht Apps standardmäßig barrierefrei. So werden beispielsweise alle Text- und interaktiven Elemente in jeder Flutter-App korrekt für Screenreader wie TalkBack und VoiceOver angezeigt.
Manchmal ist jedoch etwas Arbeit erforderlich. Bei dieser App hat der Screenreader möglicherweise Probleme, einige generierte Wortpaare auszusprechen. Während Menschen keine Probleme haben, die beiden Wörter in cheaphead zu identifizieren, spricht ein Screenreader das ph in der Mitte des Wortes möglicherweise als f aus.
Eine Lösung besteht darin, pair.asLowerCase
durch "${pair.first} ${pair.second}"
zu ersetzen. Bei der zweiten Methode wird die String-Interpolation verwendet, um aus den beiden Wörtern in pair
einen String (z. B. "cheap head"
) zu erstellen. Wenn Sie zwei separate Wörter anstelle eines zusammengesetzten Worts verwenden, können Screenreader sie richtig erkennen. Das ist für sehbehinderte Nutzer hilfreich.
Möglicherweise möchten Sie jedoch die visuelle Einfachheit von pair.asLowerCase
beibehalten. Verwenden Sie die semanticsLabel
-Eigenschaft von Text
, um den visuellen Inhalt des Text-Widgets mit einem semantischen Inhalt zu ü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}",
),
),
);
}
// ...
Screenreader sprechen jetzt jedes generierte Wortpaar richtig aus, die Benutzeroberfläche bleibt jedoch unverändert. Probieren Sie es auf Ihrem Gerät aus.
Benutzeroberfläche zentrieren
Nachdem das zufällige Wortpaar nun ansprechend präsentiert wird, ist es an der Zeit, es in der Mitte des App-Fensters bzw. des Bildschirms zu platzieren.
BigCard
ist Teil eines Column
. Standardmäßig werden untergeordnete Elemente in Spalten oben zusammengefasst. Das lässt sich jedoch überschreiben. Rufen Sie die Methode build()
von MyHomePage
auf und 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: 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 des Column
entlang der Hauptachse (vertikal) zentriert.
Die untergeordneten Elemente sind bereits entlang der Querachse der Spalte zentriert (d. h. sie sind bereits horizontal zentriert). Das Column
selbst ist jedoch nicht in der Mitte des Scaffold
zentriert. Wir können dies mit dem Widget Inspector überprüfen.
Der Widget Inspector selbst geht über den Rahmen dieses Codelabs hinaus. Sie können jedoch sehen, dass das Column
nicht die gesamte Breite der App einnimmt, sondern nur so viel horizontalen Platz, wie seine untergeordneten Elemente benötigen.
Sie können die Spalte einfach zentrieren. Bewegen Sie den Cursor auf Column
, rufen Sie das Menü Refactor (mit Ctrl+.
oder Cmd+.
) auf und wählen Sie Wrap with Center aus.
Die App sollte jetzt in etwa so aussehen:
Wenn Sie möchten, können Sie diese Einstellungen noch weiter anpassen.
- Sie können das
Text
-Widget überBigCard
entfernen. Man könnte argumentieren, dass der beschreibende Text („Eine zufällige TOLLE Idee:“) nicht mehr erforderlich ist, da die Benutzeroberfläche auch ohne ihn verständlich ist. So ist es übersichtlicher. - Sie können auch ein
SizedBox(height: 10)
-Widget zwischenBigCard
undElevatedButton
einfügen. So ist etwas mehr Abstand zwischen den beiden Widgets. DasSizedBox
-Widget nimmt nur Platz ein und rendert nichts. Sie wird häufig verwendet, um visuelle „Lücken“ zu erstellen.
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:
Im nächsten Abschnitt fügen Sie die Möglichkeit hinzu, generierte Wörter zu favorisieren („liken“).
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, wenn es eine Möglichkeit gäbe, die besten Vorschläge zu „merken“, z. B. durch eine Schaltfläche „Gefällt mir“.
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:
- Sie haben der Property
MyAppState
eine neue Property namensfavorites
hinzugefügt. Diese Property wird mit einer leeren Liste initialisiert:[]
. - Sie haben außerdem angegeben, dass die Liste nur Wortpaare enthalten darf:
<WordPair>[]
, indem Sie Generics verwendet haben. Dadurch wird Ihre App robuster. Dart weigert sich sogar, Ihre App auszuführen, wenn Sie versuchen, etwas anderes alsWordPair
hinzuzufügen. Sie können diefavorites
-Liste verwenden, da Sie wissen, dass sich darin keine unerwünschten Objekte wienull
befinden können.
- Außerdem haben Sie eine neue Methode,
toggleFavorite()
, hinzugefügt, die das aktuelle Wortpaar entweder aus der Liste der Favoriten entfernt (falls es bereits vorhanden ist) oder es hinzufügt (falls es noch nicht vorhanden ist). In beiden Fällen wird danachnotifyListeners();
aufgerufen.
Schaltfläche hinzufügen
Nachdem die Geschäftslogik erledigt ist, ist es an der Zeit, sich wieder der Benutzeroberfläche zu widmen. Wenn die Schaltfläche „Gefällt mir“ links neben der Schaltfläche „Weiter“ platziert werden soll, ist eine Row
erforderlich. Das Row
-Widget ist das horizontale Äquivalent von Column
, das Sie bereits gesehen haben.
Schließen Sie die vorhandene Schaltfläche zuerst in ein Row
ein. Gehen Sie zur Methode MyHomePage
von build()
, bewegen Sie den Cursor auf ElevatedButton
, rufen Sie das Menü Refactor mit Ctrl+.
oder Cmd+.
auf und wählen Sie Wrap with Row aus.
Wenn Sie die Änderungen speichern, sehen Sie, dass sich Row
ähnlich wie Column
verhält. Standardmäßig werden die untergeordneten Elemente links zusammengefasst. (Column
hat die untergeordneten Elemente nach oben verschoben.) Um das Problem zu beheben, können Sie denselben Ansatz wie zuvor verwenden, aber mit mainAxisAlignment
. Verwenden Sie jedoch für didaktische Zwecke mainAxisSize
. Dadurch wird Row
angewiesen, nicht den gesamten verfügbaren horizontalen Platz einzunehmen.
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 ist wieder so, wie sie vorher war.
Fügen Sie als Nächstes die Schaltfläche Gefällt mir hinzu und verbinden Sie sie mit toggleFavorite()
. Versuchen Sie es zuerst selbst, ohne sich den Codeblock unten anzusehen.
Es ist in Ordnung, wenn Sie nicht genau so vorgehen wie unten beschrieben. Tatsächlich musst du dich nicht um das Herzsymbol kümmern, es sei denn, du möchtest eine große Herausforderung.
Es ist auch völlig in Ordnung, wenn etwas nicht klappt – schließlich ist das deine erste Stunde mit Flutter.
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. Wählen Sie oben in der build
-Methode das entsprechende Symbol aus, je nachdem, ob das aktuelle Wortpaar bereits in den Favoriten enthalten ist. Beachten Sie auch hier die Verwendung von SizedBox
, um die beiden Schaltflächen etwas voneinander zu trennen.
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:
Leider kann der Nutzer die Favoriten nicht sehen. Es ist an der Zeit, unserer App einen ganz neuen Bildschirm hinzuzufügen. Wir sehen uns im nächsten Abschnitt!
7. Navigationsstreifen hinzufügen
Die meisten Apps können nicht alle Inhalte auf einem einzigen Bildschirm darstellen. Diese spezielle App könnte das wahrscheinlich, aber aus didaktischen Gründen erstellen Sie einen separaten Bildschirm für die Favoriten des Nutzers. Um zwischen den beiden Bildschirmen zu wechseln, implementieren Sie Ihr erstes StatefulWidget
.
Um so schnell wie möglich zum Kern dieses Schritts zu gelangen, teilen Sie MyHomePage
in zwei separate Widgets auf.
Wählen Sie den gesamten Code in MyHomePage
aus, löschen Sie ihn und ersetzen Sie ihn 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, sie funktioniert aber nicht. Wenn Sie in der Navigationsleiste auf ♥︎ (das Herz) klicken, passiert nichts.
Sehen Sie sich die Änderungen an.
- Der gesamte Inhalt von
MyHomePage
wird in ein neues Widget,GeneratorPage
, extrahiert. Der einzige Teil des altenMyHomePage
-Widgets, der nicht extrahiert wurde, istScaffold
. - Das neue
MyHomePage
enthält einRow
mit zwei untergeordneten Elementen. Das erste Widget istSafeArea
und das zweite einExpanded
-Widget. - Das
SafeArea
sorgt dafür, dass das untergeordnete Element nicht durch eine Hardware-Aussparung oder eine Statusleiste verdeckt wird. In dieser App wird das Widget umNavigationRail
gewrappt, um zu verhindern, dass die Navigationsschaltflächen beispielsweise durch eine mobile Statusleiste verdeckt werden. - Sie können die Zeile
extended: false
inNavigationRail
intrue
ändern. So werden die Labels neben den Symbolen angezeigt. In einem späteren Schritt erfahren Sie, wie Sie das automatisch erledigen lassen können, wenn die App genügend horizontalen Platz hat. - Die Navigationsleiste hat zwei Ziele (Startseite und Favoriten) mit den entsprechenden Symbolen und Labels. Außerdem wird die aktuelle
selectedIndex
definiert. Bei einem ausgewählten Index von null wird das erste Ziel ausgewählt, bei einem ausgewählten Index von eins das zweite Ziel usw. Derzeit ist der Wert fest auf null codiert. - Außerdem wird festgelegt, was passiert, wenn der Nutzer eines der Ziele mit
onDestinationSelected
auswählt. Derzeit gibt die App nur den angeforderten Indexwert mitprint()
aus. - Das zweite untergeordnete Element von
Row
ist dasExpanded
-Widget. Erweiterte Widgets sind in Zeilen und Spalten äußerst nützlich. Sie ermöglichen es Ihnen, Layouts zu erstellen, in denen einige untergeordnete Elemente nur so viel Platz einnehmen, wie sie benötigen (in diesem FallSafeArea
), und andere Widgets so viel Platz wie möglich einnehmen sollen (in diesem FallExpanded
).Expanded
-Widgets sind in gewisser Weise „gierig“. Wenn Sie ein besseres Gefühl für die Rolle dieses Widgets bekommen möchten, können Sie dasSafeArea
-Widget in ein anderesExpanded
-Widget einfügen. Das resultierende Layout sieht in etwa so aus:
- Zwei
Expanded
-Widgets teilen den gesamten verfügbaren horizontalen Platz untereinander auf, obwohl die Navigationsleiste nur einen kleinen Bereich auf der linken Seite benötigt. - Im
Expanded
-Widget befindet sich ein farbigesContainer
und im Container dasGeneratorPage
.
Zustandslose und zustandsorientierte Widgets
Bisher hat MyAppState
alle Ihre Anforderungen erfüllt. Deshalb sind alle Widgets, die Sie bisher geschrieben haben, zustandslos. Sie enthalten keinen eigenen veränderlichen Status. Keines der Widgets kann sich selbst ändern – Änderungen müssen über MyAppState
erfolgen.
Das ändert sich jetzt.
Sie benötigen eine Möglichkeit, den Wert von selectedIndex
der Navigationsleiste zu speichern. Sie möchten diesen Wert auch über den onDestinationSelected
-Callback ändern können.
Sie könnten selectedIndex
als weitere Property von MyAppState
hinzufügen. Und es würde funktionieren. Stellen Sie sich aber vor, wie schnell der App-Status unübersichtlich würde, wenn jedes Widget seine Werte darin speicherte.
Einige Status sind nur für ein einzelnes Widget relevant und sollten daher bei diesem Widget bleiben.
Geben Sie die StatefulWidget
ein, eine Art von Widget mit State
. Wandeln Sie MyHomePage
zuerst in ein zustandsorientiertes Widget um.
Bewegen Sie den Cursor auf die erste Zeile von MyHomePage
(die mit class MyHomePage...
beginnt) und rufen Sie das Menü Refactor (Umgestalten) mit Ctrl+.
oder Cmd+.
auf. Wählen Sie dann Convert to StatefulWidget (In StatefulWidget konvertieren) aus.
Die IDE erstellt eine neue Klasse für Sie: _MyHomePageState
. Diese Klasse erweitert State
und kann daher ihre eigenen Werte verwalten. (Es kann sich selbst ändern.) Die Methode build
aus dem alten, zustandslosen Widget wurde in _MyHomePageState
verschoben (anstatt im Widget zu bleiben). Sie wurde unverändert verschoben – es hat sich nichts an der build
-Methode geändert. Sie befindet sich jetzt nur an einem anderen Ort.
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:
- Sie führen eine neue Variable
selectedIndex
ein und initialisieren sie mit0
. - Sie verwenden diese neue Variable in der
NavigationRail
-Definition anstelle des bisherigen hartcodierten0
. - Wenn der
onDestinationSelected
-Callback aufgerufen wird, weisen Sie den neuen Wert nicht nur der Konsole zu, sondern weisen ihnselectedIndex
innerhalb einessetState()
-Aufrufs zu. Dieser Aufruf ähnelt der zuvor verwendeten MethodenotifyListeners()
. Er sorgt dafür, dass die Benutzeroberfläche aktualisiert wird.
Die Navigationsleiste reagiert jetzt auf Nutzerinteraktionen. Der erweiterte Bereich auf der rechten Seite bleibt jedoch unverändert. Das liegt daran, dass im Code nicht selectedIndex
verwendet wird, um zu bestimmen, welcher Bildschirm angezeigt wird.
selectedIndex verwenden
Fügen Sie den folgenden Code oben in die build
-Methode von _MyHomePageState
ein, 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');
}
// ...
Sehen Sie sich diesen Code an:
- Im Code wird eine neue Variable
page
vom TypWidget
deklariert. - Anschließend wird mit einer Switch-Anweisung
page
ein Bildschirm zugewiesen, je nach dem aktuellen Wert inselectedIndex
. - Da es noch kein
FavoritesPage
gibt, verwenden SiePlaceholder
. Dieses praktische Widget zeichnet ein durchgestrichenes Rechteck an der Stelle, an der Sie es platzieren, und kennzeichnet diesen Teil der Benutzeroberfläche als unfertig.
- Gemäß dem Fail-Fast-Prinzip wird mit der switch-Anweisung auch ein Fehler ausgegeben, wenn
selectedIndex
weder 0 noch 1 ist. So lassen sich Fehler von vornherein vermeiden. Wenn Sie der Navigationsleiste ein neues Ziel hinzufügen und vergessen, diesen Code zu aktualisieren, stürzt das Programm in der Entwicklung ab. So müssen Sie nicht raten, warum etwas nicht funktioniert, und Sie können keinen fehlerhaften Code in der Produktion veröffentlichen.
Da page
das Widget enthält, das Sie rechts anzeigen möchten, können Sie wahrscheinlich erraten, welche andere Änderung erforderlich ist.
So sieht _MyHomePageState
nach dieser einen verbleibenden Änderung aus:
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 unserem GeneratorPage
und dem Platzhalter, der bald zur Seite Favoriten wird.
Ansprechbarkeit
Als Nächstes machen Sie die Navigationsleiste responsiv. Das bedeutet, dass die Labels automatisch (mit extended: true
) angezeigt werden, wenn genügend Platz dafür vorhanden ist.
Flutter bietet mehrere Widgets, mit denen Sie Ihre Apps automatisch responsiv gestalten können. Wrap
ist beispielsweise ein Widget, das Row
oder Column
ähnelt und untergeordnete Elemente automatisch in die nächste „Zeile“ (als „Run“ bezeichnet) umbricht, wenn nicht genügend vertikaler oder horizontaler Platz vorhanden ist. Es gibt FittedBox
, ein Widget, das sein untergeordnetes Element automatisch an den verfügbaren Platz anpasst.
NavigationRail
zeigt Labels jedoch nicht automatisch an, wenn genügend Platz vorhanden ist, da nicht bekannt ist, was in jedem Kontext genügend Platz ist. Es liegt an Ihnen als Entwickler, diesen Aufruf zu starten.
Angenommen, Sie möchten Labels nur anzeigen, wenn MyHomePage
mindestens 600 Pixel breit ist.
Das zu verwendende Widget ist in diesem Fall LayoutBuilder
. Damit können Sie die Struktur Ihres Widgets je nach verfügbarem Platz ändern.
Verwenden Sie noch einmal das Menü Refactor von Flutter in VS Code, um die erforderlichen Änderungen vorzunehmen. Dieses Mal ist es jedoch etwas komplizierter:
- Setzen Sie den Cursor in der Methode
build
von_MyHomePageState
aufScaffold
. - Rufen Sie das Menü Refactor mit
Ctrl+.
(Windows/Linux) oderCmd+.
(Mac) auf. - Wählen Sie Mit Builder umschließen aus und drücken Sie die Eingabetaste.
- Ändern Sie den Namen des neu hinzugefügten
Builder
inLayoutBuilder
. - Ändern Sie die Liste der Callback-Parameter von
(context)
in(context, constraints)
.
Der builder
-Callback von LayoutBuilder
wird jedes Mal aufgerufen, wenn sich die Einschränkungen ändern. Das passiert beispielsweise in folgenden Fällen:
- Der Nutzer ändert die Größe des App-Fensters.
- Der Nutzer dreht sein Smartphone vom Hochformat ins Querformat oder umgekehrt.
- Einige Widgets neben
MyHomePage
werden größer, wodurch die Einschränkungen vonMyHomePage
kleiner werden.
Ihr Code kann jetzt entscheiden, ob das Label angezeigt werden soll, indem er die aktuelle constraints
abfragt. Nehmen Sie die folgende einzeilige Änderung an der build
-Methode 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,
),
),
],
),
);
});
}
}
// ...
Ihre App reagiert jetzt auf die Umgebung, z. B. Bildschirmgröße, Ausrichtung und Plattform. Mit anderen Worten: Es ist responsiv.
Die einzige Aufgabe, die noch aussteht, ist, Placeholder
durch einen tatsächlichen Favoriten-Bildschirm zu ersetzen. Das wird im nächsten Abschnitt behandelt.
8. Neue Seite hinzufügen
Erinnerst du dich an das Placeholder
-Widget, das wir anstelle der Seite Favoriten verwendet haben?
Es ist an der Zeit, das zu ändern.
Wenn Sie sich das zutrauen, können Sie diesen Schritt auch selbst ausführen. Ihr Ziel ist es, die Liste der favorites
in einem neuen zustandslosen Widget, FavoritesPage
, anzuzeigen und dieses Widget dann anstelle von Placeholder
zu verwenden.
Hier einige Hinweise:
- Wenn Sie ein
Column
mit Scrollfunktion wünschen, verwenden Sie dasListView
-Widget. - Denken Sie daran, dass Sie mit
context.watch<MyAppState>()
von jedem Widget aus auf dieMyAppState
-Instanz zugreifen können. - Wenn Sie auch ein neues Widget ausprobieren möchten, hat
ListTile
Eigenschaften wietitle
(im Allgemeinen für Text),leading
(für Symbole oder Avatare) undonTap
(für Interaktionen). Mit den bekannten Widgets lassen sich jedoch ähnliche Effekte erzielen. - In Dart können
for
-Schleifen in Collection-Literalen verwendet werden. Wennmessages
beispielsweise eine Liste von Strings enthält, können Sie Code wie den folgenden verwenden:
Wenn Sie sich mit funktionaler Programmierung besser auskennen, können Sie in Dart auch Code wie messages.map((m) => Text(m)).toList()
schreiben. Sie können natürlich auch jederzeit eine Liste von Widgets erstellen und sie in der Methode build
imperativ ergänzen.
Der Vorteil, die Seite Favoriten selbst hinzuzufügen, besteht darin, dass Sie mehr lernen, wenn Sie Ihre eigenen Entscheidungen treffen. Der Nachteil ist, dass Sie möglicherweise auf Probleme stoßen, die Sie noch nicht selbst lösen können. Denken Sie daran: Scheitern ist in Ordnung und einer der wichtigsten Bestandteile des Lernens. Niemand erwartet, dass Sie Flutter-Entwicklung in der ersten Stunde beherrschen, und Sie sollten das auch nicht tun.
Im Folgenden wird eine Möglichkeit zur Implementierung der Seite „Favoriten“ beschrieben. Die Implementierung soll Sie (hoffentlich) dazu anregen, mit dem Code zu experimentieren, die Benutzeroberfläche zu verbessern und sie an Ihre Bedürfnisse anzupassen.
Hier ist die neue Klasse FavoritesPage
:
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),
),
],
);
}
}
Das kann das Widget:
- Ruft den aktuellen Status der App ab.
- Wenn die Favoritenliste leer ist, wird die zentrierte Meldung Noch keine Favoriten angezeigt.
- 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 ein
ListTile
-Widget.
Jetzt muss nur noch das Placeholder
-Widget durch ein FavoritesPage
-Widget ersetzt werden. Und voilà!
Den endgültigen Code dieser App finden Sie im Codelab-Repository auf GitHub.
9. Nächste Schritte
Glückwunsch!
Sieh mal einer an! Sie haben ein nicht funktionales Scaffold mit einem Column
- und zwei Text
-Widgets genommen und daraus eine responsive, ansprechende kleine App gemacht.
Behandelte Themen
- Grundlagen der Funktionsweise von Flutter
- Layouts in Flutter erstellen
- Nutzerinteraktionen (z. B. Tastendrücke) mit dem App-Verhalten verknüpfen
- Flutter-Code organisieren
- App responsiv gestalten
- Einheitliches Erscheinungsbild Ihrer App
Nächste Schritte
- Experimentieren Sie weiter mit der App, die Sie in diesem Lab geschrieben haben.
- Im Code dieser erweiterten Version derselben App sehen Sie, wie Sie animierte Listen, Verläufe, Überblendungen und mehr hinzufügen können.
- Unter flutter.dev/learn finden Sie Ihren Lernpfad.