1. Einführung
Flutter ist das UI-Toolkit von Google für die Entwicklung von mobilen, Web- und Desktopanwendungen mit einer einzigen Codebasis. In diesem Codelab erstellen Sie die folgende Flutter-Anwendung:
Die Anwendung generiert kühle Namen wie "newstay", "lightstream", "mainbrake" oder "graypine". Der Nutzer kann nach dem nächsten Namen fragen, den aktuellen als Favoriten hinzufügen und die Liste der Favoriten auf einer separaten Seite ansehen. Die App reagiert auf verschiedene Bildschirmgrößen.
Lerninhalte
- Grundlagen der Funktionsweise von Flutter
- Layouts in Flutter erstellen
- Verbindung zwischen Nutzerinteraktionen (z. B. Drücken von Tasten) und App-Verhalten herstellen
- Flutter-Code organisieren
- Ihre App responsiv machen (für verschiedene Bildschirme)
- Ein einheitliches Erscheinungsbild die Funktionsweise Ihrer App
Sie beginnen mit einem einfachen Gerüst, sodass Sie direkt zu den interessanten Stellen springen können.
Hier ist Filip, der Sie durch das gesamte Codelab führt!
Klicken Sie auf „Weiter“, um das Lab zu starten.
2. Flutter-Umgebung einrichten
Editor
Um dieses Codelab so einfach wie möglich zu gestalten, gehen wir davon aus, dass Sie Visual Studio Code (VS Code) als Entwicklungsumgebung verwenden. Es ist kostenlos und funktioniert auf allen gängigen Plattformen.
Natürlich können Sie einen beliebigen Editor verwenden: Android Studio, andere IntelliJ IDEs, Emacs, Vim oder Notepad++. Sie alle sind mit Flutter kompatibel.
Wir empfehlen, für dieses Codelab VS Code zu verwenden, da die Anleitung standardmäßig VS Code-spezifische Tastenkombinationen verwendet. Es ist einfacher, Dinge wie „Klicken Sie hier“ zu sagen oder „Diese Taste drücken“ anstelle von etwas wie „Führen Sie im Editor die entsprechende Aktion aus, um X zu tun“.
Entwicklungsziel auswählen
Flutter ist ein plattformübergreifendes Toolkit. Ihre App kann unter einem der folgenden Betriebssysteme ausgeführt werden:
- iOS
- Android
- Windows
- macOS
- Linux
- web
Üblicherweise wählen Sie jedoch ein einziges Betriebssystem aus, für das Sie hauptsächlich entwickeln. Dies ist Ihr „Entwicklungsziel“, also das Betriebssystem, auf dem Ihre App während der Entwicklung ausgeführt wird.
Angenommen, Sie verwenden einen Windows-Laptop, um eine Flutter-App zu entwickeln. Wenn Sie Android als Entwicklungsziel auswählen, schließen Sie in der Regel ein Android-Gerät über ein USB-Kabel an Ihren Windows-Laptop an. Ihre App in der Entwicklung wird auf diesem angeschlossenen Android-Gerät ausgeführt. Sie können aber auch Windows als Entwicklungsziel auswählen. In diesem Fall wird Ihre App in der Entwicklung als Windows-App zusammen mit Ihrem Editor ausgeführt.
Es kann verlockend sein, das Web als Entwicklungsziel auszuwählen. Der Nachteil dieser Auswahl ist, dass Ihnen eine der nützlichsten Entwicklungsfunktionen von Flutter verloren geht: das zustandsorientierte Hot Refresh. Flutter kann Webanwendungen nicht Hot-Reloads ausführen.
Treffen Sie jetzt Ihre Entscheidung. Denken Sie daran: Sie können Ihre App später jederzeit auch auf anderen Betriebssystemen ausführen. Es ist nur, dass ein klares Entwicklungsziel den nächsten Schritt reibungsloser gestaltet.
Flutter installieren
Eine aktuelle Anleitung zur Installation des Flutter SDK finden Sie unter docs.flutter.dev.
Die Anleitung auf der Flutter-Website bezieht sich nicht nur auf die Installation des SDK selbst, sondern auch auf die Entwicklungstools und die Editor-Plug-ins. Für dieses Codelab müssen Sie nur Folgendes installieren:
- 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önnten einige dieser Fragen und Antworten (von StackOverflow) für die Fehlerbehebung hilfreich sein.
FAQ
- Wie finde ich den Pfad zum Flutter SDK?
- Was kann ich tun, wenn der Flutter-Befehl nicht gefunden wird?
- Wie behebe ich den Fehler „Warten auf einen anderen Flutter-Befehl, um die Startsperre aufzuheben“ Problem?
- Wie gebe ich Flutter an, wo sich meine Android SDK-Installation befindet?
- Wie gehe ich mit dem Java-Fehler um, wenn ich
flutter doctor --android-licenses
ausführe? - Wie gehe ich mit dem Android-
sdkmanager
-Tool um, das nicht gefunden wurde? - Was muss ich tun, wenn die Komponente „
cmdline-tools
“ fehlt Fehler? - 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
). Geben Sie „flutter new“ ein. Wählen Sie den Befehl Flutter: New Project (Flutter: Neues Projekt) aus.
Wählen Sie als Nächstes Anwendung und dann einen Ordner aus, in dem Sie Ihr Projekt erstellen möchten. Das könnte Ihr Basisverzeichnis oder etwa C:\src\
sein.
Geben Sie zum Schluss einen Namen für Ihr Projekt ein. Zum Beispiel namer_app
oder my_awesome_namer
.
Flutter erstellt nun Ihren Projektordner und öffnet ihn in VS Code.
Sie überschreiben nun den Inhalt von drei Dateien mit einem einfachen Gerüst der App.
Kopieren und Erste App einfügen
Achten Sie darauf, dass im linken Bereich von VS Code Explorer ausgewählt ist, und öffnen Sie die Datei pubspec.yaml
.
Ersetzen Sie den Inhalt dieser Datei durch Folgendes:
pubspec.yaml
name: namer_app
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 0.0.1+1
environment:
sdk: ^3.1.1
dependencies:
flutter:
sdk: flutter
english_words: ^4.0.0
provider: ^6.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true
In der Datei pubspec.yaml
sind grundlegende Informationen zu deiner App angegeben, z. B. die aktuelle Version, die Abhängigkeiten sowie die Assets, mit denen sie ausgeliefert wird.
Öffnen Sie als Nächstes eine weitere Konfigurationsdatei im Projekt: analysis_options.yaml
.
Ersetzen Sie den Inhalt durch Folgendes:
analysis_options.yaml
include: package:flutter_lints/flutter.yaml
linter:
rules:
avoid_print: false
prefer_const_constructors_in_immutables: false
prefer_const_constructors: false
prefer_const_literals_to_create_immutables: false
prefer_final_fields: false
unnecessary_breaks: true
use_key_in_widget_constructors: false
Mit dieser Datei wird festgelegt, wie streng Flutter bei der Analyse Ihres Codes sein sollte. Da dies Ihr erster Ausflug in Flutter ist, weisen Sie das Analysegerät an, es ganz entspannt anzugehen. Du kannst dies später jederzeit anpassen. Je näher Sie der Veröffentlichung einer echten Produktions-App kommen, desto strenger werden Sie den Analysator.
Öffnen Sie schließlich die Datei main.dart
im Verzeichnis lib/
.
Ersetzen Sie den Inhalt dieser Datei durch Folgendes:
lib/main.dart
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
return Scaffold(
body: Column(
children: [
Text('A random idea:'),
Text(appState.current.asLowerCase),
],
),
);
}
}
Diese 50 Codezeilen sind die gesamte App.
Im nächsten Abschnitt führen Sie die Anwendung im Debug-Modus aus und beginnen mit der Entwicklung.
4. Schaltfläche hinzufügen
Dadurch wird die Schaltfläche Weiter hinzugefügt, mit der eine neue Wortkopplung generiert werden kann.
App starten
Öffne zuerst lib/main.dart
und prüfe, ob du dein Zielgerät ausgewählt hast. Unten rechts in VS Code finden Sie eine Schaltfläche, über die das aktuelle Zielgerät angezeigt wird. Zum Ändern klicken.
Während lib/main.dart
geöffnet ist, kannst du nach „Wiedergabe“ suchen rechts oben im VS Code-Fenster und klicken Sie darauf.
Nach etwa einer Minute wird die App im Debug-Modus gestartet. Es sieht noch nicht viel aus:
First Hot Refresh
Fügen Sie dem String im ersten Text
-Objekt unten in lib/main.dart
etwas hinzu und speichern Sie die Datei (mit Ctrl+S
oder Cmd+S
). Hier einige Beispiele:
lib/main.dart
// ...
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'), // ← Example change.
Text(appState.current.asLowerCase),
],
),
);
// ...
Die App ändert sich sofort, aber das Wort bleibt gleich. Dies ist das berühmte zustandsorientierte Hot Refresh von Flutter. Ein Hot Refresh wird ausgelöst, wenn Sie Änderungen an einer Quelldatei speichern.
Häufig gestellte Fragen
- Was passiert, wenn Hot Refresh in VSCode nicht funktioniert?
- Muss ich die Taste „R“ drücken, für Hot Refresh in VSCode?
- Funktioniert Hot Refresh im Web?
- Wie entferne ich die Spalte „Debug“ Banner?
Schaltfläche hinzufügen
Fügen Sie als Nächstes eine Schaltfläche am unteren Rand von Column
direkt unter der zweiten Text
-Instanz hinzu.
lib/main.dart
// ...
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'),
Text(appState.current.asLowerCase),
// ↓ Add this.
ElevatedButton(
onPressed: () {
print('button pressed!');
},
child: Text('Next'),
),
],
),
);
// ...
Wenn Sie die Änderung speichern, wird die App wieder aktualisiert: Eine Schaltfläche wird angezeigt und in der Debug Console in VS Code wird die Meldung Schaltfläche gedrückt! angezeigt.
Ein Flutter Crashkurs in 5 Minuten
So viel Spaß es auch macht, die Debugging-Konsole anzusehen, Sie möchten, dass die Schaltfläche etwas aussagekräftigeres tun kann. Bevor Sie damit beginnen, werfen Sie jedoch einen genaueren Blick auf den Code in lib/main.dart
, um zu verstehen, wie er funktioniert.
lib/main.dart
// ...
void main() {
runApp(MyApp());
}
// ...
Ganz oben in der Datei finden Sie die Funktion main()
. In der aktuellen Form wird Flutter nur angewiesen, die in MyApp
definierte App auszuführen.
lib/main.dart
// ...
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
// ...
Die Klasse MyApp
erweitert StatelessWidget
. Widgets sind die Elemente, aus denen Sie jede Flutter-App erstellen. Wie Sie sehen, ist sogar die App selbst ein Widget.
Mit dem Code in MyApp
wird die gesamte App eingerichtet. Damit wird der App-weite Status erstellt (mehr dazu später), der App einen Namen, das visuelle Design und das „Home“. Widget, also den Startpunkt Ihrer App.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
// ...
Als Nächstes definiert die Klasse MyAppState
den Status der App. Dies ist dein erster Schritt in Flutter. Deshalb wird es in diesem Codelab einfach und fokussiert bleiben. Es gibt viele leistungsstarke Möglichkeiten, den App-Status in Flutter zu verwalten. Eine der einfachsten Erklärungen ist ChangeNotifier
, der Ansatz dieser App.
MyAppState
definiert die Daten, die die App benötigt. Derzeit enthält es nur eine einzige Variable mit dem aktuellen Zufallswortpaar. Dieses Feld wird später hinzugefügt.- Die Statusklasse erweitert
ChangeNotifier
, was bedeutet, dass sie andere über ihre eigenen Änderungen benachrichtigen kann. Wenn sich beispielsweise das aktuelle Wortpaar ändert, müssen einige Widgets in der App darüber informiert werden. - Der Status wird erstellt und mithilfe eines
ChangeNotifierProvider
für die gesamte Anwendung bereitgestellt (siehe Code oben inMyApp
). Dadurch 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
),
);
}
}
// ...
Schließlich gibt es noch MyHomePage
, das Widget, das Sie bereits geändert haben. Jede nummerierte Zeile unten entspricht einem Zeilennummernkommentar im obigen Code:
- 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 mit der Methodewatch
.- Jede
build
-Methode muss ein Widget oder – üblicher – eine verschachtelte Struktur von Widgets zurückgeben. In diesem Fall ist das Widget der obersten EbeneScaffold
. In diesem Codelab wirst du nicht mitScaffold
arbeiten. Es ist aber ein nützliches Widget, das in den meisten realen Flutter-Apps zu finden ist. Column
ist eines der einfachsten Layout-Widgets in Flutter. Dabei werden eine beliebige Anzahl von untergeordneten Elementen von oben nach unten in eine Spalte eingefügt. Standardmäßig werden die untergeordneten Elemente der Spalte visuell oben platziert. Bald werden Sie dies ändern, sodass die Spalte zentriert wird.- Sie haben dieses
Text
-Widget im ersten Schritt geändert. - Dieses zweite
Text
-Widget verwendetappState
und greift auf das einzige Mitglied dieser Klasse zu,current
(also eineWordPair
).WordPair
bietet mehrere hilfreiche Getter, z. B.asPascalCase
oderasSnakeCase
. Hier verwenden wirasLowerCase
, aber Sie können dies jetzt ändern, wenn Sie eine der Alternativen bevorzugen. - Beachten Sie, dass nachgestellte Kommas im Flutter-Code intensiv eingesetzt werden. Dieses Komma muss hier nicht stehen, da
children
das letzte (und einzig) Mitglied dieser speziellenColumn
-Parameterliste ist. Im Allgemeinen empfiehlt es sich jedoch, nachgestellte Kommas zu verwenden: Sie machen das Hinzufügen weiterer Mitglieder zum Kinderspiel und sie dienen dem automatischen Formatierer von Dart als Hinweis, dort eine neue Zeile zu platzieren. Weitere Informationen finden Sie unter Codeformatierung.
Als Nächstes verbinden Sie die Schaltfläche mit dem Status.
Ihr erstes Verhalten
Scrollen Sie zu MyAppState
und fügen Sie eine getNext
-Methode hinzu.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
// ↓ Add this.
void getNext() {
current = WordPair.random();
notifyListeners();
}
}
// ...
Die neue Methode getNext()
weist current
mit einer neuen zufälligen WordPair
neu zu. Außerdem wird notifyListeners()
aufgerufen. Diese Methode von ChangeNotifier)
sorgt dafür, dass alle Zuschauer von MyAppState
benachrichtigt werden.
Nun müssen Sie nur noch die getNext
-Methode über den Callback der Schaltfläche aufrufen.
lib/main.dart
// ...
ElevatedButton(
onPressed: () {
appState.getNext(); // ← This instead of print().
},
child: Text('Next'),
),
// ...
Speichern und die App jetzt testen. Jedes Mal, wenn Sie auf die Schaltfläche Weiter klicken, sollte ein neues Zufallswortpaar generiert werden.
Im nächsten Abschnitt gestalten Sie die Benutzeroberfläche übersichtlicher.
5. App ansprechender gestalten
So sieht die App im Moment aus.
Nicht so toll. Das Herzstück der App – das zufällig generierte Wortpaar – sollte besser sichtbar sein. Das ist schließlich der Hauptgrund dafür, dass unsere Nutzer diese App verwenden! Außerdem sind die App-Inhalte seltsam nicht mittig platziert und die gesamte App ist langweilig und schwarz. weiß.
In diesem Abschnitt wird das Design der App überarbeitet, um diese Probleme zu beheben. Das Endziel für diesen Abschnitt sieht in etwa so aus:
Widget extrahieren
Die Zeile, in der das aktuelle Wortpaar angezeigt wird, sieht jetzt so aus: Text(appState.current.asLowerCase)
. Für eine komplexere Umwandlung empfiehlt es sich, diese Zeile in ein separates Widget zu extrahieren. Die Verwendung separater Widgets für separate logische Teile Ihrer Benutzeroberfläche ist ein wichtiger Schritt, um die Komplexität in Flutter zu bewältigen.
Flutter bietet eine Refaktorierungshilfe zum Extrahieren von Widgets. Achten Sie jedoch vor der Verwendung darauf, dass die extrahierte Zeile nur auf die benötigten Informationen zugreift. Im Moment greift die Zeile auf appState
zu. Tatsächlich muss aber nur das aktuelle Wortpaar angegeben werden.
Schreiben Sie deshalb das MyHomePage
-Widget so um:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current; // ← Add this.
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'),
Text(pair.asLowerCase), // ← Change to this.
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
);
}
}
// ...
Schön. Das Text
-Widget bezieht sich nicht mehr auf die gesamte appState
.
Rufen Sie jetzt das Menü Refaktorierung auf. In VS Code haben Sie dafür zwei Möglichkeiten:
- Klicken Sie mit der rechten Maustaste auf den Code, den Sie refaktorieren möchten (in diesem Fall
Text
), und wählen Sie im Drop-down-Menü Refaktorieren... aus.
ODER
- Bewegen Sie den Cursor zu dem Code, den Sie refaktorieren möchten (in diesem Fall
Text
) und drücken SieCtrl+.
(Windows/Linux) oderCmd+.
(Mac).
Wählen Sie im Menü Refaktorieren die Option Widget extrahieren aus. Weisen Sie einen Namen zu, z. B. BigCard und klicken Sie auf Enter
.
Dadurch wird am Ende der aktuellen Datei automatisch eine neue Klasse namens BigCard
erstellt. Der Kurs sieht in etwa so aus:
lib/main.dart
// ...
class BigCard extends StatelessWidget {
const BigCard({
super.key,
required this.pair,
});
final WordPair pair;
@override
Widget build(BuildContext context) {
return Text(pair.asLowerCase);
}
}
// ...
Beachten Sie, dass die App auch während dieser Refaktorierung weiterhin funktioniert.
Hinzufügen einer Karte
Jetzt ist es an der Zeit, dieses neue Widget in die fett gedruckte Benutzeroberfläche einzubinden, die wir am Anfang dieses Abschnitts vorgestellt haben.
Suchen Sie die Klasse BigCard
und die darin enthaltene Methode build()
. Rufen Sie wie zuvor das Menü Refaktorieren im Text
-Widget auf. In diesem Fall werden Sie das Widget jedoch nicht extrahieren.
Wählen Sie stattdessen Mit Füllung umschließen aus. Dadurch wird ein neues übergeordnetes Widget mit dem Namen Padding
um das Text
-Widget erstellt. Nach dem Speichern sehen Sie, dass das zufällige Wort bereits mehr Raum hat.
Erhöhen Sie den Abstand ausgehend vom Standardwert 8.0
. Verwenden Sie beispielsweise 20
, um einen größeren Abstand zu erreichen.
Geh als Nächstes eine Ebene höher. Bewegen Sie den Mauszeiger auf das Padding
-Widget, öffnen Sie das Menü Refaktorierung und wählen Sie Mit Widget umschließen... aus.
Damit können Sie das übergeordnete Widget angeben. Gib „Card“ ein. und drücken Sie die Eingabetaste.
Dadurch werden das Padding
-Widget und damit auch das Text
mit einem Card
-Widget umschlossen.
Design und Stil
Um die Karte stärker hervorzuheben, zeichnen Sie sie mit einer kräftigen Farbe. Da es außerdem immer sinnvoll ist, ein einheitliches Farbschema beizubehalten, verwenden Sie die Theme
der App, um die Farbe auszuwählen.
Nehmen Sie die folgenden Änderungen an der Methode build()
von BigCard
vor.
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context); // ← Add this.
return Card(
color: theme.colorScheme.primary, // ← And also this.
child: Padding(
padding: const EdgeInsets.all(20),
child: Text(pair.asLowerCase),
),
);
}
// ...
Diese beiden neuen Zeilen leisten viel Arbeit:
- Zuerst fordert der Code das aktuelle Design der App mit
Theme.of(context)
an. - Dann definiert der Code die Farbe der Karte so, dass sie der Farbe der
colorScheme
-Eigenschaft des Designs entspricht. Das Farbschema enthält viele Farben.primary
ist die markanteste und prägendste Farbe der App.
Die Karte ist jetzt in der Hauptfarbe der App dargestellt:
Du kannst diese Farbe und das Farbschema der gesamten App ändern, indem du nach oben zu MyApp
scrollst und dort die Startfarbe für ColorScheme
änderst.
Achten Sie darauf, wie die Farbe reibungslos animiert wird. Dies wird als implizite Animation bezeichnet. Viele Flutter-Widgets interpolieren nahtlos zwischen Werten, sodass die Benutzeroberfläche nicht einfach „hüpft“. zwischen Bundesstaaten.
Die Schaltfläche „Hoch“ unter der Karte ändert ihre Farbe. Das ist die Leistungsfähigkeit einer App-weiten Theme
im Gegensatz zu fest codierten Werten.
TextTheme
Es gibt immer noch ein Problem mit der Karte: Der Text ist zu klein und die Farbe ist schwer zu lesen. Nehmen Sie die folgenden Änderungen an der Methode build()
von BigCard
vor, um das Problem zu beheben.
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
// ↓ Add this.
final style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(20),
// ↓ Change this line.
child: Text(pair.asLowerCase, style: style),
),
);
}
// ...
Was steckt hinter dieser Änderung?
- Mit
theme.textTheme,
greifst du auf das Schriftdesign der App zu. Dieser Kurs umfasst Mitglieder wiebodyMedium
(für Standardtext mittlerer Größe),caption
(für Bilduntertitel) oderheadlineLarge
(für große Anzeigentitel). - Die Eigenschaft
displayMedium
ist ein großer Stil, der für Anzeigetext vorgesehen ist. Das Wort Display wird hier im typografischen Sinn verwendet, z. B. als Anzeigeschrift. Die Dokumentation fürdisplayMedium
besagt, dass „Darstellungsstile 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-sicher, sodass Sie keine Methoden von Objekten aufrufen können, die möglicherweisenull
sind. In diesem Fall können Sie jedoch den!
-Operator („Bang-Operator“) verwenden, damit Dart wissen, was Sie tun. (displayMedium
ist in diesem Fall definitiv nicht null. Wir wissen aber, dass dies den Rahmen dieses Codelabs sprengen würde.) - Wenn Sie
copyWith()
fürdisplayMedium
aufrufen, wird eine Kopie des Textstils mit den von Ihnen definierten Änderungen zurückgegeben. In diesem Fall ändern Sie nur die Textfarbe. - Um die neue Farbe zu erhalten, müssen Sie wieder auf das Design der App zugreifen. Die Eigenschaft
onPrimary
des Farbschemas definiert eine Farbe, die sich gut zur Verwendung derHauptfarbe der Appder App eignet.
Die App sollte jetzt in etwa so aussehen:
Wenn Sie möchten, ändern Sie die Karte weiter. Hier einige Vorschläge:
- Mit
copyWith()
können Sie nicht nur die Farbe, sondern auch den Textstil sehr viel mehr ändern. Um die vollständige Liste der Eigenschaften zu erhalten, die Sie ändern können, setzen Sie den Cursor an eine beliebige Stelle in die Klammern voncopyWith()
und drücken SieCtrl+Shift+Space
(Windows/Linux) oderCmd+Shift+Space
(Mac). - Analog kannst du weitere Änderungen am
Card
-Widget vornehmen. Sie können beispielsweise den Schatten der Karte vergrößern, indem Sie den Wert des Parameterselevation
erhöhen. - Experimentiere mit Farben. Neben
theme.colorScheme.primary
gibt es auch.secondary
,.surface
und viele weitere. Für alle diese Farben gibt es eineonPrimary
-Entsprechung.
Inhalte besser zugänglich machen
Mit Flutter sind Apps standardmäßig verfügbar. Beispielsweise werden in jeder Flutter-App alle Texte und interaktiven Elemente in der App korrekt für Screenreader wie TalkBack und VoiceOver angezeigt.
Manchmal ist jedoch etwas Arbeit erforderlich. Bei dieser App hat der Screenreader möglicherweise Probleme bei der Aussprache einiger generierter Wortpaare. Menschen haben kein Problem, die beiden Wörter in cheaphead zu identifizieren. Ein Screenreader sagt das ph in der Mitte des Wortes jedoch möglicherweise als f aus.
Eine einfache Lösung besteht darin, pair.asLowerCase
durch "${pair.first} ${pair.second}"
zu ersetzen. Letzterer verwendet Stringinterpolation, um aus den beiden Wörtern in pair
einen String (z. B. "cheap head"
) zu erstellen. Die Verwendung von zwei separaten Wörtern anstelle eines zusammengesetzten Wortes stellt sicher, dass Screenreader diese korrekt identifizieren, und bietet sehbehinderten Nutzenden ein besseres Erlebnis.
Trotzdem solltest du die visuelle Einfachheit von pair.asLowerCase
beibehalten. Mit der Eigenschaft semanticsLabel
von Text
kannst du den visuellen Inhalt des Text-Widgets mit einem semantischen Inhalt überschreiben, der für Screenreader besser geeignet ist:
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(20),
// ↓ Make the following change.
child: Text(
pair.asLowerCase,
style: style,
semanticsLabel: "${pair.first} ${pair.second}",
),
),
);
}
// ...
Jetzt sprechen Screenreader jedes generierte Wortpaar richtig aus, die Benutzeroberfläche bleibt jedoch gleich. Probieren Sie diese Aktion aus, indem Sie einen Screenreader auf Ihrem Gerät verwenden.
Benutzeroberfläche zentrieren
Jetzt, da das zufällig ausgewählte Wortpaar optisch ansprechend ist, ist es an der Zeit, es in die Mitte des App-Fensters bzw. -Bildschirms zu platzieren.
Denken Sie zuerst daran, dass BigCard
Teil einer Column
ist. Standardmäßig werden die untergeordneten Elemente von Spalten ganz oben angezeigt. Dies lässt sich jedoch leicht überschreiben. Wechseln Sie zur build()
-Methode von MyHomePage
und nehmen Sie folgende Änderung vor:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center, // ← Add this.
children: [
Text('A random AWESOME idea:'),
BigCard(pair: pair),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
);
}
}
// ...
Dadurch werden die untergeordneten Elemente innerhalb von Column
entlang der vertikalen Hauptachse zentriert.
Die untergeordneten Elemente sind bereits entlang der Kreuzachse der Spalte zentriert, d. h. sie sind bereits horizontal zentriert. Aber Column
selbst ist nicht innerhalb der Scaffold
zentriert. Das können wir mit dem Widget Inspector überprüfen.
Der Widget Inspector selbst wird in diesem Codelab nicht behandelt. Sie können jedoch sehen, dass das hervorgehobene Column
-Objekt nicht die gesamte Breite der App einnimmt. Es nimmt nur so viel horizontalen Platz ein, wie seine Kinder benötigen.
Sie können einfach die Spalte selbst zentrieren. Bewegen Sie den Mauszeiger auf Column
, rufen Sie das Menü Refaktorierung auf (mit Ctrl+.
oder Cmd+.
) und wählen Sie Mit Mitte umbrechen aus.
Die App sollte jetzt in etwa so aussehen:
Wenn Sie möchten, können Sie dies weiter optimieren.
- Du kannst das
Text
-Widget überBigCard
entfernen. Es könnte argumentiert werden, dass der beschreibende Text („Eine zufällige Fantastische Idee:“) nicht mehr benötigt wird, da die Benutzeroberfläche auch ohne sie Sinn ergibt. So ist es auch übersichtlicher. - Du kannst auch ein
SizedBox(height: 10)
-Widget zwischenBigCard
undElevatedButton
hinzufügen. Auf diese Weise sind die beiden Widgets etwas stärker voneinander getrennt. DasSizedBox
-Widget nimmt nur Speicherplatz in Anspruch und rendert nichts selbst. Es wird häufig verwendet, um visuelle „Lücken“ zu schaffen.
Mit den optionalen Änderungen enthält MyHomePage
diesen Code:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
),
);
}
}
// ...
Die App sieht so aus:
Im nächsten Abschnitt fügen Sie die Möglichkeit hinzu, generierte Wörter als Favoriten zu markieren oder mit „Gefällt mir“ zu markieren.
6. Funktionen hinzufügen
Die App funktioniert und liefert gelegentlich sogar interessante Wortpaare. Wenn der Nutzer jedoch auf Weiter klickt, verschwindet jedes Wortpaar für immer. Es wäre besser, sich an eine Möglichkeit zu erinnern, wie eine positive Bewertung Schaltfläche.
Geschäftslogik hinzufügen
Scrollen Sie zu MyAppState
und fügen Sie den folgenden Code hinzu:
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
void getNext() {
current = WordPair.random();
notifyListeners();
}
// ↓ Add the code below.
var favorites = <WordPair>[];
void toggleFavorite() {
if (favorites.contains(current)) {
favorites.remove(current);
} else {
favorites.add(current);
}
notifyListeners();
}
}
// ...
Prüfen Sie die Änderungen:
- Du hast
MyAppState
eine neue Property namens „favorites
“ hinzugefügt. Dieses Attribut wird mit einer leeren Liste initialisiert:[]
. - Außerdem haben Sie mithilfe von generics angegeben, dass die Liste nur Wortpaare enthalten darf:
<WordPair>[]
. Dies trägt dazu bei, deine App robuster zu machen, denn Dart verweigert die Ausführung deiner App, wenn du versuchst, etwas anderes alsWordPair
hinzuzufügen. Sie können wiederum diefavorites
-Liste verwenden und wissen, dass sich nie unerwünschte Objekte wienull
darin verstecken können.
- Sie haben außerdem die neue Methode
toggleFavorite()
hinzugefügt, mit der das aktuelle Wortpaar entweder aus der Favoritenliste entfernt wird (sofern es bereits vorhanden ist) oder mit der es hinzugefügt wird (falls es noch nicht vorhanden ist). In beiden Fällen wird mit dem Code anschließendnotifyListeners();
aufgerufen.
Schaltfläche hinzufügen
Mit der „Geschäftslogik“ ist es an der Zeit, wieder an der Benutzeroberfläche zu arbeiten. Platzieren der „Gefällt mir“-Angabe auf die Schaltfläche Schaltfläche erfordert Row
. Das Row
-Widget ist die horizontale Entsprechung zu Column
, das Sie bereits gesehen haben.
Verpacke die vorhandene Schaltfläche zuerst in einem Row
. Gehen Sie zur build()
-Methode von MyHomePage
, platzieren Sie den Mauszeiger auf ElevatedButton
, rufen Sie mit Ctrl+.
oder Cmd+.
das Menü Refaktorieren auf und wählen Sie Mit Zeile umbrechen aus.
Beim Speichern werden Sie feststellen, dass sich Row
ähnlich wie Column
verhält. Standardmäßig werden die untergeordneten Elemente auf der linken Seite angezeigt. (Column
hat seine untergeordneten Elemente ganz oben angeordnet.) Um dieses Problem zu beheben, können Sie denselben Ansatz wie zuvor verwenden, nur mit mainAxisAlignment
. Verwende jedoch mainAxisSize
fürdidaktische Zwecke (Lernen). Damit wird Row
angewiesen, nicht den gesamten verfügbaren horizontalen Bereich zu belegen.
Nehmen Sie die folgende Änderung vor:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min, // ← Add this.
children: [
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
),
);
}
}
// ...
Die Benutzeroberfläche wird wieder auf den ursprünglichen Zustand zurückgesetzt.
Fügen Sie als Nächstes die Schaltfläche Gefällt mir hinzu und verknüpfen Sie sie mit toggleFavorite()
. Versuchen Sie es zuerst selbst, ohne sich den Codeblock unten anzusehen.
Es ist in Ordnung, wenn Sie es anders machen als unten. Um das Herzsymbol brauchst du dir sogar keine Gedanken zu machen, es sei denn, du möchtest dir wirklich eine große Herausforderung stellen.
Sie können auch scheitern. Das ist Ihre erste Stunde mit Flutter.
Hier ist eine Möglichkeit, die zweite Schaltfläche zu MyHomePage
hinzuzufügen. Verwenden Sie dieses Mal den ElevatedButton.icon()
-Konstruktor, um eine Schaltfläche mit einem Symbol zu erstellen. Oben in der Methode build
wählen Sie das entsprechende Symbol aus, je nachdem, ob sich das aktuelle Wortpaar bereits in den Favoriten befindet. Beachte auch, dass du wieder SizedBox
verwendest, damit die beiden Schaltflächen ein wenig voneinander entfernt sind.
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
// ↓ Add this.
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
// ↓ And this.
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
),
);
}
}
// ...
Die App sollte so aussehen:
Der Nutzer kann die Favoriten nicht sehen. Als Nächstes fügen wir unserer App einen separaten Bildschirm hinzu. Bis zum nächsten Abschnitt!
7. Navigationsstreifen hinzufügen
Bei den meisten Apps passt nicht alles auf einen einzigen Bildschirm. Diese App könnte dies wahrscheinlich tun, aber aus informativen Gründen erstellen Sie einen separaten Bildschirm für die Favoriten der Nutzenden. Um zwischen den beiden Bildschirmen zu wechseln, implementierst du zuerst deine erste StatefulWidget
.
Teile MyHomePage
in zwei separate Widgets auf, um diesen Schritt so schnell wie möglich zu verarbeiten.
Wählen Sie alle Elemente in MyHomePage
aus, löschen Sie sie und ersetzen Sie sie durch den folgenden Code:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: 0,
onDestinationSelected: (value) {
print('selected: $value');
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: GeneratorPage(),
),
),
],
),
);
}
}
class GeneratorPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
);
}
}
// ...
Nach dem Speichern sehen Sie, dass die visuelle Seite der Benutzeroberfläche fertig ist, funktioniert aber nicht. Wenn du in der Navigationsleiste auf ♥︎ (das Herz) klickst, passiert nichts.
Sehen Sie sich die Änderungen an.
- Beachten Sie zuerst, dass der gesamte Inhalt von
MyHomePage
in ein neues Widget extrahiert wird:GeneratorPage
. Der einzige Teil des 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-Notch oder einer Statusleiste verdeckt wird. In dieser App umschließt das WidgetNavigationRail
, damit beispielsweise die Navigationsschaltflächen nicht von einer mobilen Statusleiste verdeckt werden. - Sie können die Linie
extended: false
in NavigationRail intrue
ändern. Daraufhin werden die Labels neben den Symbolen angezeigt. In einem späteren Schritt erfahren Sie, wie dies automatisch geschieht, wenn die App genügend horizontalen Platz hat. - Die Navigationsstrecke hat zwei Ziele (Start und Favoriten) mit den entsprechenden Symbolen und Labels. Außerdem wird damit der aktuelle
selectedIndex
definiert. Ein ausgewählter Index von null wählt das erste Ziel aus, ein ausgewählter Index von Eins wählt das zweite Ziel aus und so weiter. Für den Moment ist er hartcodiert auf null. - In der Navigationsstrecke ist auch definiert, was passiert, wenn der Nutzer mit
onDestinationSelected
eines der Ziele auswählt. Derzeit gibt die Anwendung nur den angeforderten Indexwert mitprint()
aus. - Das zweite untergeordnete Element von
Row
ist dasExpanded
-Widget. Maximierte Widgets sind in Zeilen und Spalten äußerst nützlich. Damit können Sie Layouts ausdrücken, bei denen einige untergeordnete Elemente nur so viel Platz einnehmen, wie sie benötigen (in diesem FallSafeArea
). Andere Widgets sollten so viel Platz wie möglich einnehmen (in diesem FallExpanded
).Expanded
-Widgets könnten zum Beispiel „gierig“ sein. Wenn du ein besseres Gefühl für die Rolle dieses Widgets bekommen möchtest, kannst du dasSafeArea
-Widget mit einem anderenExpanded
-Element umschließen. Das resultierende Layout sieht in etwa so aus:
- Zwei
Expanded
-Widgets teilen den gesamten verfügbaren horizontalen Bereich unter sich auf, obwohl die Navigationsleiste nur ein kleines Stück links benötigt. - Im
Expanded
-Widget befindet sich eine farbigeContainer
und im Container dasGeneratorPage
.
Zustandslose und zustandsorientierte Widgets im Vergleich
Bisher hat MyAppState
alle deine Anforderungen in Bundesstaaten abgedeckt. Aus diesem Grund sind alle bislang erstellten Widgets zustandslos. Sie enthalten keine eigenen veränderlichen Status. Keines der Widgets kann sich selbst ändern. Sie müssen MyAppState
durchlaufen.
Dies wird sich bald ändern.
Der Wert für selectedIndex
der Navigationsstrecke muss in irgendeiner Weise festgehalten werden. Du möchtest diesen Wert auch innerhalb des onDestinationSelected
-Callbacks ändern können.
Du könntest selectedIndex
als weitere Property von MyAppState
hinzufügen. Und es würde funktionieren. Sie können sich jedoch vorstellen, dass der App-Status schnell über die Vernunft hinauswachsen würde, wenn jedes Widget seine Werte darin speichern würde.
Ein Status ist nur für ein einzelnes Widget relevant, daher sollte er bei diesem Widget bleiben.
Geben Sie StatefulWidget
ein, eine Art von Widget, das State
enthält. Konvertieren Sie zuerst MyHomePage
in ein zustandsorientiertes Widget.
Platzieren Sie den Cursor in der ersten Zeile von MyHomePage
(die mit class MyHomePage...
beginnt) und rufen Sie mit Ctrl+.
oder Cmd+.
das Menü Refaktorierung auf. Wählen Sie dann Convert to StatefulWidget aus.
Die IDE erstellt eine neue Klasse für Sie: _MyHomePageState
. Diese Klasse erweitert State
und kann daher ihre eigenen Werte verwalten. (Kann sich selbst ändern.) Beachten Sie auch, dass die Methode build
aus dem alten, zustandslosen Widget in _MyHomePageState
verschoben wurde (anstatt im Widget zu bleiben). Sie wurde wörtlich verschoben. In der Methode build
hat sich nichts geändert. Jetzt lebt es nur noch woanders.
setState
Das neue zustandsorientierte Widget muss nur eine Variable erfassen: selectedIndex
. Nehmen Sie die folgenden drei Änderungen an _MyHomePageState
vor:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0; // ← Add this property.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex, // ← Change to this.
onDestinationSelected: (value) {
// ↓ Replace print with this.
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: GeneratorPage(),
),
),
],
),
);
}
}
// ...
Prüfen Sie die Änderungen:
- Sie fügen die neue Variable
selectedIndex
ein 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 ihnselectedIndex
innerhalb einessetState()
-Aufrufs zu, anstatt ihn nur in der Konsole auszugeben. Dieser Aufruf ähnelt der zuvor verwendeten MethodenotifyListeners()
. Damit wird sichergestellt, dass die Benutzeroberfläche aktualisiert wird.
Die Navigationsschiene reagiert jetzt auf Nutzerinteraktionen. Der erweiterte Bereich auf der rechten Seite bleibt jedoch gleich. Das liegt daran, dass der Code nicht selectedIndex
verwendet, um zu bestimmen, welcher Bildschirm angezeigt wird.
selectedIndex verwenden
Platzieren Sie den folgenden Code am Anfang der build
-Methode von _MyHomePageState
direkt vor return Scaffold
:
lib/main.dart
// ...
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
// ...
Untersuchen Sie dieses Code-Snippet:
- Der Code deklariert die neue Variable
page
vom TypWidget
. - Dann weist eine Switch-Anweisung
page
einen Bildschirm gemäß dem aktuellen Wert inselectedIndex
zu. - Da noch kein
FavoritesPage
vorhanden ist, verwendePlaceholder
. ein praktisches Widget, das ein gekreuztes Rechteck zeichnet, wo immer Sie es platzieren, und diesen Teil der Benutzeroberfläche als unfertig markiert.
- Entsprechend dem Fail-Fast-Prinzip sorgt die Switch-Anweisung auch dafür, dass ein Fehler ausgegeben wird, wenn
selectedIndex
weder 0 noch 1 ist. So lassen sich spätere Fehler vermeiden. Wenn Sie jemals ein neues Ziel zum Navigationspfad hinzufügen und vergessen, diesen Code zu aktualisieren, stürzt das Programm während der Entwicklung ab (im Gegensatz dazu, dass Sie erraten können, warum etwas nicht funktioniert, oder Sie können einen fehlerhaften Code in die Produktion veröffentlichen).
Da page
jetzt das Widget enthält, das auf der rechten Seite angezeigt werden soll, können Sie wahrscheinlich raten, welche weiteren Änderungen erforderlich sind.
Hier ist _MyHomePageState
nach der letzten verbleibenden Änderung:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: page, // ← Here.
),
),
],
),
);
}
}
// ...
Die App wechselt jetzt zwischen GeneratorPage
und dem Platzhalter, der bald zur Seite Favoriten wird.
Ansprechbarkeit
Machen Sie als Nächstes die Navigationsschiene responsiv. Das heißt, die Labels werden automatisch angezeigt (mithilfe von extended: true
), wenn genügend Platz für sie vorhanden ist.
Flutter bietet verschiedene Widgets, mit denen du dafür sorgen kannst, dass deine Apps automatisch reagieren. Zum Beispiel ist Wrap
ein Widget ähnlich Row
oder Column
, das untergeordnete Elemente automatisch bis zur nächsten „Zeile“ einschließt. („Laufen“), wenn nicht genügend vertikaler oder horizontaler Platz vorhanden ist. Hier gibt es FittedBox
. Dabei handelt es sich um ein Widget, bei dem das untergeordnete Element automatisch entsprechend Ihren Angaben in den verfügbaren Bereich passt.
NavigationRail
zeigt Labels jedoch nicht automatisch an, wenn genügend Platz vorhanden ist, da nicht in jedem Kontext herausgefunden werden kann, wie viel Platz vorhanden ist. Die Entscheidung liegt bei Ihnen, dem Entwickler.
Angenommen, Labels werden nur angezeigt, wenn MyHomePage
mindestens 600 Pixel breit ist.
Das zu verwendende Widget ist in diesem Fall LayoutBuilder
. Damit können Sie Ihre Widget-Struktur abhängig vom verfügbaren Speicherplatz ändern.
Nehmen Sie die erforderlichen Änderungen in VS Code über das Flutter-Menü Refaktorieren wieder vor. Dieses Mal ist es jedoch etwas komplizierter:
- Bewegen Sie den Cursor in der Methode
build
von_MyHomePageState
aufScaffold
. - Rufen Sie das Menü Refaktorierung 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 der neu hinzugefügten
Builder
zuLayoutBuilder
. - Ändere die Liste der Callback-Parameter von
(context)
zu(context, constraints)
.
Der builder
-Callback von LayoutBuilder
wird jedes Mal aufgerufen, wenn sich die Einschränkungen ändern. Das passiert z. B. in folgenden Fällen:
- Der Nutzer ändert die Größe des App-Fensters.
- Der Nutzer dreht sein Smartphone vom Hochformat ins Querformat oder zurück.
- Ein Widget neben
MyHomePage
wird größer, wodurch die Einschränkungen vonMyHomePage
verringert werden - usw.
Jetzt kann Ihr Code entscheiden, ob das Label angezeigt werden soll, indem der aktuelle constraints
abgefragt wird. Nehmen Sie die folgende einzeilige Änderung an der Methode build
von _MyHomePageState
vor:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return LayoutBuilder(builder: (context, constraints) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: constraints.maxWidth >= 600, // ← Here.
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: page,
),
),
],
),
);
});
}
}
// ...
Jetzt reagiert Ihre App auf ihre Umgebung, wie z. B. Bildschirmgröße, Ausrichtung und Plattform. Mit anderen Worten, sie ist responsiv!
Sie müssen lediglich den Placeholder
-Bildschirm durch einen tatsächlichen Favoriten-Bildschirm ersetzen. Das wird im nächsten Abschnitt behandelt.
8. Neue Seite hinzufügen
Erinnern Sie sich an das Widget Placeholder
, das wir anstelle der Seite Favoriten verwendet haben?
Es ist Zeit, dieses Problem zu beheben.
Wenn du experimentierfreudig bist, versuche diesen Schritt alleine zu machen. Ihr Ziel ist es, die Liste der favorites
im neuen zustandslosen Widget FavoritesPage
anzuzeigen und dann dieses Widget anstelle von Placeholder
anzuzeigen.
Hier einige Tipps:
- Wenn ein
Column
scrollen soll, verwenden Sie dasListView
-Widget. - Denken Sie daran, mit
context.watch<MyAppState>()
von einem beliebigen Widget aus auf die InstanzMyAppState
zuzugreifen. - Wenn du auch ein neues Widget ausprobieren möchtest, findest du in
ListTile
Eigenschaften wietitle
(allgemein für Text),leading
(für Symbole oder Avatare) undonTap
(für Interaktionen). Mit bereits bekannten Widgets können Sie jedoch ähnliche Effekte erzielen. - Dart ermöglicht die Verwendung von
for
-Schleifen innerhalb von Sammlungsliteralen. Wennmessages
beispielsweise eine Liste mit Strings enthält, können Sie Code wie den folgenden verwenden:
Wenn Sie dagegen mehr Erfahrung mit der funktionalen Programmierung haben, können Sie in Dart auch Code wie messages.map((m) => Text(m)).toList()
schreiben. Natürlich können Sie auch jederzeit eine Liste mit Widgets erstellen und diese unbedingt in der Methode build
hinzufügen.
Wenn Sie die Seite Favoriten selbst hinzufügen, können Sie durch eigene Entscheidungen mehr darüber erfahren. Der Nachteil ist, dass Sie möglicherweise auf Probleme stoßen, die Sie noch nicht alleine lösen können. Denken Sie daran: Scheitern ist in Ordnung und eines der wichtigsten Elemente beim Lernen. Niemand erwartet, dass Sie die Flutter-Entwicklung in der ersten Stunde erfolgreich umsetzen – und das auch.
Im Folgenden findest du nur eine Möglichkeit, die Favoritenseite zu implementieren. Die Art und Weise der Implementierung wird Sie (hoffentlich) dazu inspirieren, mit dem Code zu experimentieren. Verbessern Sie die Benutzeroberfläche und gestalten Sie sie an Ihre Anforderungen.
Hier ist die neue FavoritesPage
-Klasse:
lib/main.dart
// ...
class FavoritesPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
if (appState.favorites.isEmpty) {
return Center(
child: Text('No favorites yet.'),
);
}
return ListView(
children: [
Padding(
padding: const EdgeInsets.all(20),
child: Text('You have '
'${appState.favorites.length} favorites:'),
),
for (var pair in appState.favorites)
ListTile(
leading: Icon(Icons.favorite),
title: Text(pair.asLowerCase),
),
],
);
}
}
So funktioniert das Widget:
- Sie ruft den aktuellen Status der App ab.
- Wenn die Favoritenliste leer ist, wird in der Mitte die folgende Nachricht angezeigt: Noch keine Favoriten*.*
- Andernfalls wird eine scrollbare Liste angezeigt.
- Die Liste beginnt mit einer Zusammenfassung, z. B. Du hast 5 Favoriten*.*
- Der Code durchläuft dann alle Favoriten und erstellt für jeden einzelnen ein
ListTile
-Widget.
Jetzt müssen Sie nur noch das Placeholder
-Widget durch ein FavoritesPage
ersetzen. Und voilà!
Sie können den endgültigen Code dieser App im Codelab-Repository auf GitHub abrufen.
9. Nächste Schritte
Glückwunsch!
Sieh mal einer an! Sie haben ein nicht funktionales Gerüst mit einem Column
- und zwei Text
-Widgets in eine responsive, kleine App umgewandelt.
Behandelte Themen
- Grundlagen der Funktionsweise von Flutter
- Layouts in Flutter erstellen
- Verbindung zwischen Nutzerinteraktionen (z. B. Drücken von Tasten) und App-Verhalten herstellen
- Flutter-Code organisieren
- App responsiv machen
- Ein einheitliches Erscheinungsbild die Funktionsweise Ihrer App
Nächster Schritt
- Experimentieren Sie mit der App, die Sie in diesem Lab geschrieben haben.
- Sehen Sie sich den Code dieser erweiterten Version derselben App an. Dort erfahren Sie, wie Sie unter anderem animierte Listen, Farbverläufe und Überblendungen hinzufügen können.
- Unter flutter.dev/learn können Sie Ihren Lernpfad verfolgen.