1. Einführung
Flutter ist das UI-Toolkit von Google, mit dem Anwendungen für Mobilgeräte, Web und Computer auf einer gemeinsamen Codebasis erstellt werden können. In diesem Codelab erstellen Sie die folgende Flutter-Anwendung:
Die Anwendung generiert cool klingende Namen wie „newstay“, „lightstream“, „mainbrake“ oder „graypine“. Der Nutzer kann den nächsten Namen anfordern, den aktuellen als Favoriten speichern und sich die Liste der gespeicherten Namen auf einer separaten Seite ansehen. Die App ist für verschiedene Bildschirmgrößen responsiv.
Lerninhalte
- Grundlagen der Flutter-Funktionsweise
- Layouts in Flutter erstellen
- Nutzerinteraktionen (z. B. das Drücken von Schaltflächen) mit dem App-Verhalten verknüpfen
- Flutter-Code organisieren
- Ihre App für verschiedene Bildschirme responsiv gestalten
- Für ein einheitliches Erscheinungsbild Ihrer App sorgen
Sie beginnen mit einem einfachen Scaffolding, damit Sie direkt zu den interessanten Teilen springen können.
Und hier führt Filip Sie durch das gesamte Codelab.
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. Sie ist kostenlos und funktioniert auf allen gängigen Plattformen.
Sie können natürlich jeden beliebigen Editor verwenden: Android Studio, andere IntelliJ-IDEs, Emacs, Vim oder Notepad++. Alle funktionieren mit Flutter.
Wir empfehlen für dieses Codelab die Verwendung von VS Code, da in der Anleitung standardmäßig VS Code-spezifische Tastenkürzel verwendet werden. Es ist einfacher, etwas wie „Hier klicken“ oder „Diese Taste drücken“ 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 jedem 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 in erster Linie entwickeln. Das ist Ihr „Entwicklungsziel“, also das Betriebssystem, unter dem Ihre App während der Entwicklung ausgeführt wird.
Angenommen, Sie verwenden einen Windows-Laptop, um eine Flutter-App zu entwickeln. Wenn Sie Android als Entwicklungsziel auswählen, verbinden Sie in der Regel ein Android-Gerät über ein USB-Kabel mit Ihrem Windows-Laptop und die in Entwicklung befindliche App wird auf diesem angeschlossenen Android-Gerät ausgeführt. Sie können aber auch Windows als Entwicklungsziel auswählen. Das bedeutet, dass Ihre in Entwicklung befindliche App als Windows-App neben Ihrem Editor ausgeführt wird.
Es kann verlockend sein, das Web als Entwicklungsziel auszuwählen. Der Nachteil dieser Entscheidung besteht darin, dass Sie eine der nützlichsten Entwicklungsfunktionen von Flutter verlieren: den zustandsorientierten Hot Reload. Mit Flutter können keine Webanwendungen per Hot Reload aktualisiert werden.
Treffen Sie jetzt Ihre Wahl. Denken Sie daran: Sie können Ihre App später auch auf anderen Betriebssystemen ausführen. Wenn Sie jedoch ein klares Entwicklungsziel vor Augen haben, ist der nächste Schritt einfacher.
Flutter installieren
Die aktuellste Anleitung zum Installieren des Flutter SDK finden Sie immer unter docs.flutter.dev.
Die Anleitung auf der Flutter-Website deckt nicht nur die Installation des SDK selbst ab, sondern auch die Tools für das Entwicklungsziel und die Editor-Plug-ins. Für dieses Codelab müssen Sie nur Folgendes installieren:
- Flutter SDK
- Visual Studio Code mit dem Flutter-Plug-in
- 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önnen Ihnen einige dieser Fragen und Antworten (von StackOverflow) bei der Fehlerbehebung helfen.
FAQ
- Wie finde ich den Pfad zum 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 sage ich Flutter, wo sich meine Android SDK-Installation befindet?
- Was kann ich tun, wenn beim Ausführen von
flutter doctor --android-licenses
ein Java-Fehler auftritt? - Was kann ich tun, wenn das Android-
sdkmanager
-Tool nicht gefunden wird? - Was kann ich tun, wenn die Fehlermeldung „
cmdline-tools
-Komponente fehlt“ angezeigt wird? - Wie kann ich CocoaPods auf Apple Silicon (M1) ausführen?
- 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
oder 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 das Projekt erstellen möchten. Das kann Ihr Basisverzeichnis oder etwas Ähnliches wie C:\src\
sein.
Geben Sie abschließend einen Namen für das Projekt ein. Etwas wie namer_app
oder my_awesome_namer
.
Flutter erstellt jetzt den Projektordner und VS Code öffnet ihn.
Sie überschreiben jetzt den Inhalt von drei Dateien mit einem einfachen Scaffold der App.
Ursprüngliche App kopieren und einfügen
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' # Remove this line if you wish to publish to pub.dev
version: 0.0.1+1
environment:
sdk: ^3.6.0
dependencies:
flutter:
sdk: flutter
english_words: ^4.0.0
provider: ^6.1.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter:
uses-material-design: true
Die Datei pubspec.yaml
enthält grundlegende Informationen zu Ihrer App, z. B. die aktuelle Version, die Abhängigkeiten und die Assets, die mitgeliefert werden.
Öffnen Sie als Nächstes eine weitere 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
In dieser Datei wird festgelegt, wie streng Flutter bei der Analyse Ihres Codes sein soll. Da dies Ihr erster Ausflug in Flutter ist, bitten Sie den Analyzer, es nicht zu übertreiben. Sie können die Einstellungen später jederzeit anpassen. Je näher Sie der Veröffentlichung einer Produktions-App kommen, desto strenger sollten Sie die Einstellungen des Analysetools wählen.
Öffnen Sie abschließend 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(
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 bisher.
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, um ein neues Wortpaar zu generieren.
App starten
Öffnen Sie zuerst lib/main.dart
und prüfen Sie, ob das Zielgerät ausgewählt ist. Rechts unten in VS Code sehen Sie eine Schaltfläche, auf der das aktuelle Zielgerät angezeigt wird. Klicken Sie darauf, um sie zu ändern.
Wenn lib/main.dart
geöffnet ist, suchen Sie oben rechts im VS Code-Fenster nach der Schaltfläche „Wiedergabe“ und klicken Sie darauf.
Nach etwa einer Minute wird Ihre App im Debug-Modus gestartet. Das sieht noch nicht nach viel aus:
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 Zufallswort jedoch gleich bleibt. Das ist der berühmte zustandsorientierte Hot Reload von Flutter. Der Hot-Reload wird ausgelöst, wenn Sie Änderungen an einer Quelldatei speichern.
Häufig gestellte Fragen
- Was kann ich tun, wenn Hot Reload in VSCode nicht funktioniert?
- Muss ich in VSCode die Taste „R“ drücken, um den Hot-Reload auszuführen?
- 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-Konsole in VS Code die Meldung button pressed! (Schaltfläche gedrückt) angezeigt.
Flutter-Crashkurs in 5 Minuten
Auch wenn es Spaß macht, die Debug-Konsole zu beobachten, sollte die Schaltfläche etwas Sinnvolleres tun. Bevor wir uns damit befassen, sehen wir uns den Code in lib/main.dart
genauer 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 seiner aktuellen Form weist er Flutter nur an, 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. Er erstellt den app-weiten Status (mehr dazu später), benennt die App, definiert das visuelle Design und legt das „Start“-Widget fest, den Ausgangspunkt Ihrer App.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
// ...
Als Nächstes wird in der MyAppState
-Klasse der Status der App definiert. Da dies Ihr erster Ausflug in Flutter ist, ist dieses Codelab einfach und prägnant. Es gibt viele leistungsstarke Möglichkeiten, den App-Status in Flutter zu verwalten. Eine der am einfachsten zu erklärenden Methoden ist ChangeNotifier
, der Ansatz dieser App.
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 können das später ergänzen.- 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 mithilfe eines
ChangeNotifierProvider
erstellt und der gesamten App zur Verfügung gestellt (siehe Code oben inMyApp
). So kann jedes Widget in der App den Status abrufen.
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
),
);
}
}
// ...
Und zu guter Letzt MyHomePage
, das Widget, das Sie bereits geändert haben. Jede nummerierte Zeile unten entspricht einem Kommentar mit einer Zeilennummer im Code oben:
- Für jedes Widget wird eine
build()
-Methode definiert, die automatisch jedes Mal aufgerufen wird, wenn sich die Umstände des Widgets ändern, damit es immer auf dem neuesten Stand ist. MyHomePage
überwacht mithilfe der Methodewatch
Änderungen am aktuellen Status der App.- Jede
build
-Methode muss ein Widget oder (häufiger) 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 hilfreiches Widget und wird in der überwiegenden Mehrheit der Flutter-Apps verwendet. Column
ist eines der grundlegendsten Layout-Widgets in Flutter. Es nimmt eine beliebige Anzahl von untergeordneten Elementen und ordnet sie von oben nach unten in einer Spalte an. Standardmäßig werden die untergeordneten Elemente der Spalte oben angezeigt. Sie ändern das gleich so, dass die Spalte zentriert ist.- Sie haben dieses
Text
-Widget im ersten Schritt geändert. - Dieses zweite
Text
-Widget nimmtappState
an und greift auf das einzige Mitglied dieser Klasse zu,current
(eineWordPair
).WordPair
bietet mehrere hilfreiche Getter wieasPascalCase
oderasSnakeCase
. Hier verwenden wirasLowerCase
, Sie können dies aber jetzt ändern, wenn Sie eine der Alternativen bevorzugen. - Beachten Sie, dass in Flutter-Code häufig Schlusskommas verwendet werden. Dieses Komma ist hier nicht erforderlich, da
children
das letzte (und auch einzige) Mitglied dieserColumn
-Parameterliste ist. Es ist jedoch im Allgemeinen eine gute Idee, abschließende Kommas zu verwenden: Sie erleichtern das Hinzufügen weiterer Mitglieder und dienen auch als Hinweis für den automatischen Formatierer von Dart, dort einen neuen Zeilenumbruch einzufügen. Weitere Informationen finden Sie unter Codeformatierung.
Als Nächstes verbinden Sie die Schaltfläche mit dem Status.
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 getNext()
-Methode wird current
eine neue zufällige WordPair
zugewiesen. Außerdem wird notifyListeners()
aufgerufen, eine Methode von ChangeNotifier)
, die dafür sorgt, dass alle Nutzer, die sich MyAppState
ansehen, benachrichtigt werden.
Es bleibt nur noch, die Methode getNext
über den Callback der Schaltfläche aufzurufen.
lib/main.dart
// ...
ElevatedButton(
onPressed: () {
appState.getNext(); // ← This instead of print().
},
child: Text('Next'),
),
// ...
Speichern Sie die App und testen Sie sie jetzt. Jedes Mal, wenn Sie auf die Schaltfläche Weiter klicken, 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 so gut. Das Herzstück der App – das zufällig generierte Wortpaar – sollte besser sichtbar sein. Schließlich ist das der Hauptgrund, warum unsere Nutzer diese App verwenden. Außerdem ist der App-Inhalt seltsam nicht zentriert und die gesamte App ist langweilig schwarz-weiß.
In diesem Abschnitt werden diese Probleme anhand des App-Designs angegangen. Das Endziel für diesen Abschnitt sieht in etwa so aus:
Widget extrahieren
Die Zeile, die für die Anzeige des aktuellen Wortpaars verantwortlich ist, sieht jetzt so aus: Text(appState.current.asLowerCase)
. Wenn Sie die Anzeige komplexer gestalten möchten, sollten Sie diese Zeile in ein separates Widget verschieben. Separate Widgets für separate logische Teile Ihrer Benutzeroberfläche sind eine wichtige Möglichkeit, die Komplexität in Flutter zu verwalten.
Flutter bietet einen Refactoring-Helfer zum Extrahieren von Widgets. Bevor Sie ihn verwenden, sollten Sie jedoch prüfen, ob die extrahierte Zeile nur auf das zugreift, was sie benötigt. Derzeit greift die Zeile auf appState
zu, muss aber eigentlich nur wissen, was das aktuelle Wortpaar ist.
Ändern Sie das MyHomePage
-Widget daher so:
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 (Umstrukturieren) auf. In VS Code haben Sie dazu zwei Möglichkeiten:
- Klicken Sie mit der rechten Maustaste auf den Code, den Sie umstrukturieren möchten (in diesem Fall
Text
), und wählen Sie im Drop-down-Menü Umstrukturieren… aus.
ODER
- Bewegen Sie den Cursor auf den Code, den Sie umstrukturieren möchten (in diesem Fall
Text
), und drücken SieCtrl+.
(Windows/Linux) oderCmd+.
(Mac).
Wählen Sie im Menü Refactor (Umstrukturieren) die Option Extract Widget (Widget extrahieren) aus. Weisen Sie einen Namen wie BigCard zu und klicken Sie auf Enter
.
Dadurch wird am Ende der aktuellen Datei automatisch eine neue Klasse namens BigCard
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 nach diesem Refactoring weiterhin funktioniert.
Hinzufügen einer Karte
Jetzt ist es an der Zeit, dieses neue Widget in das mutige UI-Element zu verwandeln, das wir uns zu Beginn dieses Abschnitts vorgestellt haben.
Suchen Sie die Klasse BigCard
und die Methode build()
darin. Rufen Sie wie zuvor das Menü Umstrukturieren im Text
-Widget auf. Diesmal extrahieren Sie das Widget jedoch nicht.
Wählen Sie stattdessen Mit Abstand umbrechen aus. Dadurch wird um das Text
-Widget ein neues übergeordnetes Widget mit dem Namen Padding
erstellt. Nach dem Speichern sehen Sie, dass das Zufallswort bereits mehr Platz hat.
Erhöhen Sie den Abstand über den Standardwert 8.0
hinaus. 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, öffnen Sie das Menü Umstrukturieren und wählen Sie Mit Widget umschließen… aus.
So können Sie das übergeordnete Widget angeben. Geben Sie „Karte“ ein und drücken Sie die Eingabetaste.
Dadurch wird das Padding
-Widget und damit auch das Text
in ein Card
-Widget eingefügt.
Design und Stil
Um die Karte stärker hervorzuheben, malen Sie sie in einer kräftigeren Farbe. Da es immer eine gute Idee ist, ein einheitliches Farbschema beizubehalten, wählen Sie die Farbe mit der Theme
der App aus.
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 übernehmen eine Menge Arbeit:
- Zuerst wird mit
Theme.of(context)
das aktuelle Design der App angefordert. - Anschließend wird die Farbe der Karte so definiert, dass sie mit der
colorScheme
-Eigenschaft des Themas übereinstimmt. Das Farbschema enthält viele Farben undprimary
ist die auffälligste, charakteristische Farbe der App.
Die Karte ist jetzt in der Primärfarbe der App dargestellt:
Sie können diese Farbe und das Farbschema der gesamten App ändern, indem Sie zu MyApp
scrollen und dort die Startfarbe für die ColorScheme
ändern.
Beachten Sie, wie flüssig die Farbe animiert wird. Dies wird als implizite Animation bezeichnet. Viele Flutter-Widgets interpolieren nahtlos zwischen Werten, damit die Benutzeroberfläche nicht einfach zwischen den Status „springt“.
Auch die hervorgehobene Schaltfläche unter der Karte ändert die Farbe. Das ist der Vorteil einer app-weiten Theme
im Vergleich zur Hartcodierung von Werten.
TextTheme
Die Karte hat aber noch ein Problem: Der Text ist zu klein und die 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),
),
);
}
// ...
Gründe für diese Änderung:
- Mit
theme.textTheme,
greifen Sie auf das Schriftart-Design der App zu. Zu dieser Klasse gehören Elemente wiebodyMedium
(für Standardtext in mittlerer Größe),caption
(für Bildunterschriften) oderheadlineLarge
(für große Überschriften). - Das Attribut
displayMedium
ist ein großer Stil für Anzeigentext. Das Wort Display wird hier im typografischen Sinne verwendet, z. B. in Displayschrift. In der Dokumentation fürdisplayMedium
steht, dass „Display-Styles für kurzen, wichtigen Text reserviert sind“ – genau unser Anwendungsfall. - Die
displayMedium
-Eigenschaft des Themas könnte theoretischnull
sein. Dart, die Programmiersprache, in der Sie diese App schreiben, ist nullsicher. Daher können Sie keine Methoden von Objekten aufrufen, die potenziellnull
sind. In diesem Fall können Sie jedoch den!
-Operator („Ausrufezeichen-Operator“) verwenden, um Dart zu versichern, dass Sie wissen, was Sie tun. (displayMedium
ist in diesem Fall definitiv nicht null. Wie wir das wissen, geht jedoch über den Rahmen dieses Codelabs hinaus.) - Wenn Sie
copyWith()
aufdisplayMedium
anwenden, 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, rufen Sie noch einmal das Design der App auf. Die Property
onPrimary
des Farbschemas definiert eine Farbe, die sich gut für die Verwendung in der primären Farbe der App eignet.
Die App sollte jetzt in etwa so aussehen:
Sie können die Karte nach Belieben weiter ändern. Hier einige Vorschläge:
- Mit
copyWith()
können Sie nicht nur die Farbe, sondern auch viele andere Aspekte des Textstils ändern. Wenn Sie eine vollständige Liste der Eigenschaften aufrufen möchten, die Sie ändern können, setzen Sie den Cursor an eine beliebige Stelle in den Klammern voncopyWith()
und drücken Sie die TasteCtrl+Shift+Space
(Windows/Linux) oderCmd+Shift+Space
(Mac). - Ebenso können Sie weitere Einstellungen für das
Card
-Widget vornehmen. Sie können beispielsweise den Schatten der Karte vergrößern, indem Sie den Wert des Parameterselevation
erhöhen. - Probieren Sie verschiedene Farben aus. Neben
theme.colorScheme.primary
gibt es auch.secondary
,.surface
und eine Vielzahl anderer. Alle diese Farben haben ihreonPrimary
-Entsprechungen.
Inhalte besser zugänglich machen
Mit Flutter sind Apps standardmäßig barrierefrei. Beispielsweise werden in jeder Flutter-App alle Text- und interaktiven Elemente in der App für Screenreader wie TalkBack und VoiceOver korrekt angezeigt.
Manchmal ist jedoch etwas Arbeit erforderlich. Bei dieser App hat der Screenreader möglicherweise Probleme, einige der generierten Wortpaare auszusprechen. Menschen haben keine Probleme, die beiden Wörter in billigkopf zu identifizieren. Ein Screenreader könnte das ph in der Mitte des Wortes jedoch als f aussprechen.
Eine einfache Lösung besteht darin, pair.asLowerCase
durch "${pair.first} ${pair.second}"
zu ersetzen. Bei letzterem wird mithilfe der Stringinterpolation ein String (z. B. "cheap head"
) aus den beiden Wörtern in pair
erstellt. Wenn Sie zwei separate Wörter anstelle eines zusammengesetzten Wortes verwenden, werden sie von Screenreadern korrekt erkannt. Das erleichtert sehbehinderten Nutzern die Nutzung.
Sie sollten jedoch die visuelle Einfachheit von pair.asLowerCase
beibehalten. Verwenden Sie das Attribut semanticsLabel
von Text
, um den visuellen Inhalt des Text-Widgets durch einen 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}",
),
),
);
}
// ...
Jetzt sprechen Screenreader jedes generierte Wortpaar korrekt aus, die Benutzeroberfläche bleibt jedoch gleich. Verwenden Sie dazu einen Screenreader auf Ihrem Gerät.
Benutzeroberfläche zentrieren
Jetzt, da das zufällige Wortpaar visuell ansprechend präsentiert wird, ist es an der Zeit, es in der Mitte des Fensters/Bildschirms der App zu platzieren.
Denken Sie daran, dass BigCard
Teil eines Column
ist. Standardmäßig werden die untergeordneten Elemente von Spalten nach oben verschoben. Das lässt sich aber ganz einfach ü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 mittig im Scaffold
ausgerichtet. Das können wir mit dem Widget-Prüftool überprüfen.
Der Widget-Inspektor selbst fällt nicht in den Rahmen dieses Codelabs. Sie sehen aber, dass das Column
, wenn es hervorgehoben ist, nicht die gesamte Breite der App einnimmt. Es nimmt nur so viel horizontalen Raum ein, wie seine untergeordneten Elemente benötigen.
Sie können die Spalte einfach zentrieren. Bewegen Sie den Mauszeiger 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:
Sie können diese Einstellungen bei Bedarf noch weiter anpassen.
- Sie können das
Text
-Widget überBigCard
entfernen. Man könnte argumentieren, dass der beschreibende Text („Eine zufällige GUTE Idee:“) nicht mehr benötigt wird, da die Benutzeroberfläche auch ohne ihn Sinn macht. Und so ist es übersichtlicher. - Sie können auch ein
SizedBox(height: 10)
-Widget zwischenBigCard
undElevatedButton
einfügen. So ist die Trennung zwischen den beiden Widgets etwas größer. DasSizedBox
-Widget nimmt nur Platz ein und rendert nichts von selbst. Sie werden 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:
Im nächsten Abschnitt fügen Sie die Möglichkeit hinzu, generierte Wörter zu den Favoriten hinzuzufügen.
6. Funktionen hinzufügen
Die App funktioniert und bietet 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. eine „Mag ich“-Schaltfläche.
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
MyAppState
die neue Propertyfavorites
hinzugefügt. Diese Property wird mit einer leeren Liste initialisiert:[]
. - Außerdem haben Sie angegeben, dass die Liste nur Wortpaare enthalten darf:
<WordPair>[]
, wobei generische Begriffe verwendet werden. Dadurch wird Ihre App robuster. Dart führt Ihre App nicht aus, wenn Sie versuchen, ihr etwas anderes alsWordPair
hinzuzufügen. Sie können diefavorites
-Liste dann mit der Gewissheit verwenden, dass sich dort keine unerwünschten Objekte (wienull
) verstecken können.
- Außerdem haben Sie eine neue Methode hinzugefügt,
toggleFavorite()
, mit der das aktuelle Wortpaar entweder aus der Liste der Favoriten entfernt (falls es bereits dort vorhanden ist) oder hinzugefügt wird (falls es noch nicht dort vorhanden ist). In beiden Fällen ruft der Code danachnotifyListeners();
auf.
Schaltfläche hinzufügen
Nachdem die „Geschäftslogik“ erledigt ist, ist es an der Zeit, wieder an der Benutzeroberfläche zu arbeiten. Wenn die Schaltfläche „Mag ich“ links neben der Schaltfläche „Weiter“ platziert werden soll, ist ein Row
erforderlich. Das Row
-Widget ist das horizontale Äquivalent zu Column
, das Sie bereits kennen.
Setzen Sie die vorhandene Schaltfläche zuerst in Row
. Rufen Sie die build()
-Methode von MyHomePage
auf, bewegen Sie den Mauszeiger auf die ElevatedButton
, rufen Sie mit Ctrl+.
oder Cmd+.
das Menü Refactor auf und wählen Sie Wrap with Row aus.
Beim Speichern sehen Sie, dass Row
ähnlich wie Column
funktioniert: Standardmäßig werden die untergeordneten Elemente links zusammengefasst. (Column
hat seine untergeordneten Elemente nach oben verschoben.) Sie können das Problem beheben, indem Sie denselben Ansatz wie zuvor, aber mit mainAxisAlignment
verwenden. Verwenden Sie jedoch mainAxisSize
für didaktische (Lern-)Zwecke. Dadurch wird Row
angewiesen, nicht den gesamten horizontalen Bereich 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 wie zuvor.
Fügen Sie als Nächstes die Schaltfläche Mag ich hinzu und verbinden Sie sie mit toggleFavorite()
. Versuchen Sie zuerst, dies selbst zu tun, ohne sich den Codeblock unten anzusehen.
Es ist in Ordnung, wenn Sie nicht genau so vorgehen, wie unten beschrieben. Machen Sie sich keine Gedanken um das Herzsymbol, es sei denn, Sie möchten sich einer großen Herausforderung stellen.
Es ist auch völlig in Ordnung, wenn Sie Fehler machen – schließlich ist dies Ihre erste Stunde mit Flutter.
Hier ist eine Möglichkeit, MyHomePage
die zweite Schaltfläche hinzuzufügen. Verwenden Sie diesmal den Konstruktor ElevatedButton.icon()
, um eine Schaltfläche mit einem Symbol zu erstellen. Wählen Sie oben in der Methode build
das entsprechende Symbol aus, je nachdem, ob sich das aktuelle Wortpaar bereits in den Favoriten befindet. Beachten Sie auch, dass hier wieder SizedBox
verwendet wird, 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 eigenen Bildschirm hinzuzufügen. Bis zum nächsten Abschnitt!
7. Navigationsstreifen hinzufügen
In den meisten Apps ist es nicht möglich, alle Inhalte auf einem einzigen Bildschirm anzuzeigen. Bei dieser App wäre das wahrscheinlich möglich, aber zu didaktischen Zwecken erstellen Sie einen separaten Bildschirm für die Favoriten der Nutzer. Um zwischen den beiden Bildschirmen zu wechseln, implementieren Sie Ihre erste StatefulWidget
.
Damit Sie so schnell wie möglich zum Wesentlichen dieses Schritts kommen, teilen Sie MyHomePage
in zwei separate Widgets auf.
Wählen Sie den gesamten MyHomePage
-Code 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 du in der Navigationsleiste auf das Herz ♥︎ klickst, passiert nichts.
Prüfen Sie die Änderungen.
- Der gesamte Inhalt von
MyHomePage
wird in ein neues Widget namensGeneratorPage
extrahiert. Der einzige Teil des altenMyHomePage
-Widgets, der nicht extrahiert wurde, istScaffold
. - Die neue
MyHomePage
enthält eineRow
mit zwei untergeordneten Elementen. Das erste Widget istSafeArea
und das zweite einExpanded
-Widget. - Das
SafeArea
sorgt dafür, dass das untergeordnete Element nicht von einer Hardware-Kerbe oder einer Statusleiste verdeckt wird. In dieser App wird das Widget umNavigationRail
herum gewickelt, damit die Navigationsschaltflächen nicht beispielsweise von einer Statusleiste auf einem Mobilgerät verdeckt werden. - Sie können die Zeile
extended: false
in NavigationRail intrue
ändern. Die Labels werden dann neben den Symbolen angezeigt. In einem späteren Schritt erfahren Sie, wie Sie das automatisch tun, wenn in der App genügend horizontaler Platz vorhanden ist. - Die Navigationsleiste hat zwei Ziele (Startseite und Favoriten) mit den entsprechenden Symbolen und Labels. Außerdem wird der aktuelle
selectedIndex
definiert. Wenn der Index „0“ ist, wird das erste Ziel ausgewählt, wenn er „1“ ist, das zweite Ziel usw. Derzeit ist es auf „0“ hartcodiert. - Außerdem wird in der Navigationsleiste 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 des
Row
ist dasExpanded
-Widget. Maximierte Widgets sind in Zeilen und Spalten äußerst nützlich. Sie ermöglichen Layouts, 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 sollten (in diesem FallExpanded
).Expanded
-Widgets sind „gierig“. Wenn Sie sich ein besseres Bild von der Rolle dieses Widgets machen möchten, können Sie dasSafeArea
-Widget in ein anderesExpanded
-Widget einbetten. Das resultierende Layout sieht in etwa so aus:
- Zwei
Expanded
-Widgets teilen sich den gesamten verfügbaren horizontalen Bereich, obwohl für die Navigationsleiste eigentlich nur ein kleiner Bereich links benötigt wird. - Im
Expanded
-Widget befindet sich ein farbigerContainer
und im Container dieGeneratorPage
.
Zustandslose Widgets im Vergleich zu zustandsorientierten Widgets
Bisher hat MyAppState
alle Ihre Anforderungen an den Status abgedeckt. Deshalb sind alle Widgets, die Sie bisher geschrieben haben, zustandslos. Sie enthalten keinen eigenen veränderbaren Status. Keines der Widgets kann selbst geändert werden. Dazu muss MyAppState
verwendet werden.
Das ändert sich jetzt.
Sie müssen den Wert von selectedIndex
der Navigationsleiste speichern können. Außerdem möchten Sie diesen Wert über den onDestinationSelected
-Callback ändern können.
Sie könnten selectedIndex
als weitere Property von MyAppState
hinzufügen. Und es würde funktionieren. Sie können sich aber vorstellen, dass der App-Status schnell unangemessen groß werden würde, wenn jedes Widget seine Werte darin speichern würde.
Einige Status sind nur für ein einzelnes Widget relevant und sollten daher bei diesem Widget bleiben.
Geben Sie das StatefulWidget
ein, einen Widget-Typ mit State
. Konvertieren Sie zuerst MyHomePage
in ein zustandsorientiertes Widget.
Bewegen Sie den Cursor auf die erste Zeile von MyHomePage
(die mit class MyHomePage...
beginnt) und rufen Sie mit Ctrl+.
oder Cmd+.
das Menü Refactor auf. Wählen Sie dann In StatefulWidget konvertieren aus.
Die IDE erstellt einen neuen Kurs für Sie: _MyHomePageState
. Diese Klasse erweitert State
und kann daher eigene Werte verwalten. (Sie kann sich selbst ändern.) Beachten Sie auch, dass die build
-Methode aus dem alten, zustandslosen Widget in die _MyHomePageState
-Methode verschoben wurde (anstelle im Widget zu bleiben). Sie wurde unverändert verschoben – an der build
-Methode hat sich nichts geändert. Sie befindet sich jetzt nur noch an einem anderen Ort.
setState
Für das neue zustandsorientierte Widget muss nur eine Variable erfasst werden: 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 ein,
selectedIndex
, und initialisieren sie mit0
. - Sie verwenden diese neue Variable in der
NavigationRail
-Definition anstelle der bisher hartcodierten0
. - Wenn der
onDestinationSelected
-Callback aufgerufen wird, weisen Sie den neuen Wert nicht einfach in der Konsole aus, sondernselectedIndex
in einemsetState()
-Aufruf 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 maximierte 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 der 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
page
in einer Switch-Anweisung ein Bildschirm zugewiesen, der dem aktuellen Wert inselectedIndex
entspricht. - Da es noch kein
FavoritesPage
gibt, verwenden SiePlaceholder
. Dieses praktische Widget zeichnet an jeder Stelle, an der Sie es platzieren, ein durchgestrichenes Rechteck, um diesen Teil der Benutzeroberfläche als unvollständig zu kennzeichnen.
- Gemäß dem Fail-Fast-Prinzip wird mit der Switch-Anweisung auch ein Fehler geworfen, wenn
selectedIndex
weder 0 noch 1 ist. So lassen sich spätere Fehler vermeiden. Wenn Sie der Navigationsleiste ein neues Ziel hinzufügen und vergessen, diesen Code zu aktualisieren, stürzt das Programm in der Entwicklungsphase ab. Sie müssen also nicht raten, warum etwas nicht funktioniert, oder fehlerhaften Code in der Produktion veröffentlichen.
Da page
jetzt das Widget enthält, das rechts angezeigt werden soll, können Sie sich wahrscheinlich denken, welche weitere Änderung erforderlich ist.
Hier ist _MyHomePageState
nach der letzten Ä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 der 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 bei dem untergeordnete Elemente automatisch in die nächste Zeile (genannt „Lauf“) umgebrochen werden, 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 jedoch nicht automatisch Labels an, wenn genügend Platz vorhanden ist, da es nicht weiß, was in jedem Kontext ausreichend ist. Diese Entscheidung liegt in Ihrer Verantwortung.
Angenommen, Sie möchten Labels nur anzeigen lassen, wenn MyHomePage
mindestens 600 Pixel breit ist.
In diesem Fall ist das Widget LayoutBuilder
zu verwenden. So können Sie den Widget-Baum je nach verfügbarem Platz ändern.
Verwenden Sie noch einmal das Menü Refactor (Umstrukturieren) von Flutter in VS Code, um die erforderlichen Änderungen vorzunehmen. Diesmal ist es jedoch etwas komplizierter:
- Bewegen Sie den Cursor in der
build
-Methode von_MyHomePageState
aufScaffold
. - Rufen Sie das Menü Umstrukturieren mit
Ctrl+.
(Windows/Linux) oderCmd+.
(Mac) auf. - Wähle Mit Builder umschließen aus und drücke die Eingabetaste.
- Ändern Sie den Namen des neu hinzugefügten
Builder
inLayoutBuilder
. - Ändern Sie die Liste der Rückrufparameter von
(context)
in(context, constraints)
.
Der builder
-Callback von LayoutBuilder
wird jedes Mal aufgerufen, wenn sich die Einschränkungen ändern. Das kann beispielsweise in folgenden Fällen passieren:
- Der Nutzer ändert die Größe des App-Fensters.
- Der Nutzer dreht sein Smartphone vom Hoch- ins Querformat oder umgekehrt.
- Ein Widget neben
MyHomePage
wird größer, wodurch die Einschränkungen fürMyHomePage
kleiner werden - usw.
Jetzt kann Ihr Code entscheiden, ob das Label angezeigt werden soll, indem er die aktuelle constraints
abfragt. Nehmen Sie die folgende Änderung an einer Zeile 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,
),
),
],
),
);
});
}
}
// ...
Jetzt reagiert Ihre App auf die Umgebung, z. B. auf Bildschirmgröße, Ausrichtung und Plattform. Mit anderen Worten: Sie ist responsiv.
Jetzt müssen Sie nur noch das Placeholder
durch einen echten Favoriten-Bildschirm ersetzen. Das wird im nächsten Abschnitt behandelt.
8. Neue Seite hinzufügen
Erinnern Sie sich an das Placeholder
-Widget, das wir anstelle der Seite Favoriten verwendet haben?
Es ist an der Zeit, das zu ändern.
Wenn Sie experimentierfreudig sind, versuchen Sie, diesen Schritt selbst auszuführen. Sie möchten die Liste der favorites
in einem neuen zustandslosen Widget, FavoritesPage
, anzeigen und dann dieses Widget anstelle der Placeholder
anzeigen lassen.
Hier einige Tipps:
- Wenn Sie ein scrollbares
Column
wünschen, verwenden Sie dasListView
-Widget. - Denken Sie daran, dass Sie über
context.watch<MyAppState>()
von jedem Widget aus auf dieMyAppState
-Instanz zugreifen können. - Wenn Sie auch ein neues Widget ausprobieren möchten, bietet
ListTile
Eigenschaften wietitle
(in der Regel für Text),leading
(für Symbole oder Avatare) undonTap
(für Interaktionen). Mit den Ihnen bereits bekannten Widgets können Sie jedoch ähnliche Effekte erzielen. - In Dart können
for
-Schleifen in Sammlungsliteralen verwendet werden. Wennmessages
beispielsweise eine Liste von Strings enthält, kann der Code so aussehen:
Wenn Sie mit funktionaler Programmierung vertraut sind, können Sie mit 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 dann der build
-Methode hinzufügen.
Wenn Sie die Seite Favoriten selbst hinzufügen, lernen Sie mehr, weil Sie Ihre eigenen Entscheidungen treffen. Der Nachteil ist, dass Sie auf Probleme stoßen könnten, die Sie noch nicht selbst lösen können. Denken Sie daran: Fehler sind in Ordnung und gehören zu den wichtigsten Elementen des Lernens. Niemand erwartet, dass Sie die Flutter-Entwicklung in der ersten Stunde beherrschen, und das sollten Sie auch nicht.
Im Folgenden wird nur eine Möglichkeit zur Implementierung der Seite „Favoriten“ beschrieben. Die Implementierung wird Sie (hoffentlich) dazu inspirieren, mit dem Code zu experimentieren, die Benutzeroberfläche zu verbessern und sie zu Ihrer eigenen zu machen.
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 Widget bietet folgende Funktionen:
- Er ruft den aktuellen Status der App ab.
- Wenn die Liste der Favoriten leer ist, wird zentriert die Meldung Noch keine Favoriten angezeigt.
- Andernfalls wird eine scrollbare Liste angezeigt.
- Die Liste beginnt mit einer Zusammenfassung, z. B. Sie haben 5 Favoriten.
- Der Code durchläuft dann alle Favoriten und erstellt für jeden ein
ListTile
-Widget.
Jetzt müssen Sie nur noch das Placeholder
-Widget durch ein FavoritesPage
ersetzen. Et voilà!
Den fertigen Code dieser App finden Sie im Codelab-Repository auf GitHub.
9. Nächste Schritte
Glückwunsch!
Sieh mal einer an! Sie haben ein nicht funktionsfähiges Scaffold mit einem Column
- und zwei Text
-Widgets in eine responsive, ansprechende kleine App verwandelt.
Behandelte Themen
- Grundlagen der Flutter-Funktionsweise
- Layouts in Flutter erstellen
- Nutzerinteraktionen (z. B. das Drücken von Schaltflächen) mit dem App-Verhalten verknüpfen
- Flutter-Code organisieren
- App responsiv gestalten
- Für ein einheitliches Erscheinungsbild Ihrer App sorgen
Nächster Schritt
- Experimentieren Sie weiter mit der App, die Sie in diesem Lab erstellt haben.
- Im Code dieser erweiterten Version derselben App sehen Sie, wie Sie animierte Listen, Farbverläufe, Überblendungen und mehr hinzufügen können.
- Weitere Informationen finden Sie unter flutter.dev/learn.