Widget-Grundlagen
Wir bauen die Oberfläche der Wetter-App Schritt für Schritt mit Flutter auf. Am Ende dieser Seite zeigt die App das Wetter einer Stadt mit Fake-Daten an, genau wie die Android-App nach Schritt 4.
Code: Repository auf GitHub , Schritte 0 bis 4.
Die Notizen enthalten den vollständigen Code jeder Datei, sodass sich die App durch Kopieren und Einfügen nachbauen lässt. Die Folien zeigen nur Ausschnitte. Wo auch die Notizen kürzen, verlinken sie die Datei im Repository mit dem Tag des Schritts.
Flutter und Dart
Flutter ist ein Open-Source-Framework von Google für Apps auf Android, iOS, Web und Desktop aus einer Codebasis. Programmiert wird in Dart .
- Flutter zeichnet die Oberfläche selbst, statt native UI-Elemente zu verwenden
- dadurch sieht die App überall fast gleich aus, auch auf alten OS-Versionen
- Details wie AppBar und Scrollen passt Flutter an die Plattform an
- Dart wird für Android und iOS in nativen Maschinencode kompiliert
Compose und Flutter verfolgen dasselbe Prinzip: Die Oberfläche wird deklarativ als Baum verschachtelter Bausteine beschrieben. Der Unterschied liegt darunter: Compose erzeugt eine Android-App, Flutter bringt eine eigene Rendering-Engine mit und zeichnet jedes Pixel selbst, auf Android, iOS und im Browser.
Das Aussehen hängt deshalb nicht von der Android- oder iOS-Version ab.
Ganz gleich ist die App trotzdem nicht: Flutter passt das Verhalten an
die Plattform an. Auf iOS steht der Titel der AppBar mittig, Listen
federn am Ende zurück, neue Screens schieben sich von rechts herein und
lassen sich per Wischgeste schließen; die Systemschrift ist San
Francisco statt Roboto. Wer mehr iOS-Optik möchte, verwendet z. B.
Switch.adaptive oder die Cupertino-Widgets. React Native geht den
umgekehrten Weg: Es verwendet die nativen UI-Elemente und sieht dadurch
automatisch nach der jeweiligen Plattform aus.
Im Oktober 2024 entstand mit Flock ein Community-Fork von Flutter, angestoßen von Matt Carroll, einem ehemaligen Mitglied des Flutter-Teams (Hintergrund ). Flock sollte Flutter ergänzen und Fixes schneller ausliefern, nicht ersetzen.
Stand Oktober 2026: Im Flock-Repository gibt es seit Dezember 2025 keine neuen Commits, Releases gab es nie. Für die Vorlesung und das Projekt verwenden wir das Original von Google.
Projekt in VS Code anlegen
Die Flutter-Dokumentation empfiehlt VS Code mit der Flutter-Extension.
- Extension Flutter installieren (bringt Dart mit)
- Command Palette → Flutter: New Project → Empty Application
- Projektname:
weather_flutter_tif24 - Gerät unten rechts in der Statusleiste wählen, starten mit F5
Fehlt das Flutter-SDK, bietet die Extension an, es herunterzuladen
(Download SDK). Als Ordner eignet sich ~/develop bzw.
C:\Users\<Name>\develop: nicht der Projektordner, kein Cloud-Ordner wie
OneDrive, keine Leerzeichen oder Umlaute im Pfad (sonst z. B.
C:\develop). Danach Add SDK to PATH bestätigen und VS Code neu
starten. Wer einen Paketmanager nutzt, kann Flutter auch darüber
installieren, z. B. mit Homebrew unter macOS
(brew install --cask flutter), Chocolatey unter Windows
(choco install flutter) oder Snap unter Ubuntu
(sudo snap install flutter --classic); aktualisiert wird dann
ebenfalls über den Paketmanager. flutter doctor im Terminal zeigt, ob
alles eingerichtet ist.
Den Android-Emulator kennen wir schon: Er kommt aus dem Device Manager von Android Studio. Android Studio muss dafür installiert sein, aber nicht laufen. In VS Code erscheint der Emulator in der Geräteauswahl (auch über Flutter: Select Device). Ohne Emulator läuft die App auch in Chrome oder als macOS-App.
Projektnamen in Dart bestehen aus Kleinbuchstaben und Unterstrichen.
Das Referenz-Repository heißt WeatherFlutterTIF24, das Projekt darin
weather_flutter_tif24. Ein Git-Repository legt Flutter nicht an, das
geht mit git init im Projektordner.
Android Studio mit dem Flutter-Plugin funktioniert genauso. Wer dort schon zu Hause ist, kann dabei bleiben. In der Vorlesung verwenden wir VS Code, um zu zeigen, dass Flutter an keine IDE gebunden ist.
Ausgangspunkt (schritt-00) ist die Vorlage Empty Application. In
lib/main.dart bleibt nur das Nötigste übrig:
import 'package:flutter/material.dart';
void main() {
runApp(const WeatherApp());
}
class WeatherApp extends StatelessWidget {
const WeatherApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Wetter',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),
home: const Scaffold(body: SafeArea(child: Placeholder())),
);
}
}runApp startet die App mit dem obersten Widget. MaterialApp richtet
Theme und Navigation ein, home ist der erste Screen. Placeholder
zeichnet ein durchgestrichenes Rechteck, bis dort echter Inhalt steht.
Zusätzlich liegt im Repository eine Datei AGENTS.md mit Regeln für
Coding Agents (siehe Navigation und API).
Projektstruktur
Der Dart-Code liegt in lib/, nach Aufgaben in Ordner aufgeteilt.
lib/
├── main.dart
├── ui/ Screens und Widgets, ViewModels
├── model/ Datenmodell: City, CurrentWeather
└── data/ Datenquellen: Fake-Daten, Repositorys
└── api/ DTOs für die API (ab Schritt 9)
pubspec.yaml Name, Abhängigkeiten, Assets
android/ ios/ web/ Plattform-Projekte, selten angefasstDie Ordner sind dieselben wie in der Android-App. Pfade auf dieser und
den folgenden Seiten (z. B. ui/weather_screen.dart) beziehen sich auf
lib/. Dateinamen schreibt man in Dart in snake_case, Klassennamen in
PascalCase. Importiert wird mit relativen Pfaden, z. B.
import '../model/city.dart';.
pubspec.yaml entspricht build.gradle.kts und
libs.versions.toml zusammen: Hier stehen die Abhängigkeiten (Pakete
von pub.dev ).
Die Ordner android/ und ios/ enthalten echte native Projekte, die
Flutter beim Bauen verwendet. Angepasst werden sie nur für Dinge wie
Berechtigungen oder App-Icons.
Tipps für VS Code
| Aktion | macOS | Windows/Linux |
|---|---|---|
| Quick Fix, z. B. fehlender Import | ⌘ . | Ctrl+. |
| Code formatieren (Format Document) | ⇧ ⌥ F | Shift+Alt+F |
| Umbenennen, überall (Rename Symbol) | F2 | F2 |
| Zur Definition springen | ⌘ + Klick | Ctrl+Klick |
| Parameter einer Funktion anzeigen | ⇧ ⌘ Leertaste | Ctrl+Shift+Leertaste |
| Command Palette | ⇧ ⌘ P | Ctrl+Shift+P |
Besonders praktisch: Quick Fix auf einem Widget bietet Wrap with Column, Wrap with Padding, Remove this widget usw. an. Damit lässt sich der Widget-Baum umbauen, ohne Klammern zu zählen.
Snippets sparen Tipparbeit: stless + Tab erzeugt ein
StatelessWidget, stful + Tab ein StatefulWidget.
Formatieren beim Speichern: Settings → „Format On Save“ aktivieren.
Widgets
In Flutter ist alles ein Widget: Text, Abstände, Layouts, ganze Screens.
Neue Datei ui/weather_screen.dart:
import 'package:flutter/material.dart';
class WeatherScreen extends StatelessWidget {
const WeatherScreen({super.key});
@override
Widget build(BuildContext context) {
return const Column(children: [Text('Lörrach'), Text('14 °C')]);
}
}Ein Widget ist eine Klasse. build beschreibt, wie es aussieht, und
gibt genau ein Widget zurück. Mehrere Kinder brauchen deshalb ein Layout
wie Column.
Flutter arbeitet wie Compose deklarativ: Wir beschreiben, was
angezeigt werden soll. Ändern sich die Daten, ruft Flutter build erneut
auf.
| Compose | Flutter |
|---|---|
Funktion mit @Composable | Klasse, die von StatelessWidget erbt |
| Parameter der Funktion | Felder der Klasse, Konstruktor |
| Inhalt im Funktionsrumpf | Rückgabewert von build |
Column { … } (Trailing Lambda) | Column(children: [ … ]) |
modifier | eigene Widgets und Eigenschaften (siehe unten) |
Konventionen für Widgets:
const-Konstruktor mit{super.key}:keybrauchen wir erst bei Listen, er gehört aber zur Konvention- Felder sind
final, ein Widget ist unveränderlich constvor einem Widget sagt Flutter, dass es sich nie ändert; es wird nur einmal erzeugt
Damit WeatherScreen erscheint, importiert main.dart die neue Datei
und setzt das Widget statt des Placeholder ein:
Code: lib/main.dart (schritt-01)
lib/main.dart (schritt-01)import 'package:flutter/material.dart';
import 'ui/weather_screen.dart';
void main() {
runApp(const WeatherApp());
}
class WeatherApp extends StatelessWidget {
const WeatherApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Wetter',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),
home: const Scaffold(body: SafeArea(child: WeatherScreen())),
);
}
}SafeArea hält den Inhalt aus Statusleiste und Kamera-Aussparung heraus,
wie safeDrawingPadding() in Compose.
Hot Reload: Speichern genügt, die Änderung ist in unter einer Sekunde im laufenden Emulator.
- Hot Reload (Speichern): Code neu laden, State bleibt erhalten
- Hot Restart (⇧ ⌘ F5): App neu starten, State geht verloren
- neuer Build nötig bei Änderungen an
pubspec.yamloder nativem Code
Hot Reload übernimmt die Rolle der @Preview in Android Studio, nur
direkt im Emulator und mit echter Interaktion. Am besten ausprobieren:
Text ändern, speichern, Ergebnis ansehen.
Manche Änderungen kommen mit Hot Reload nicht an, z. B. neue
Startwerte von State oder Änderungen in main(). Dann hilft
Hot Restart.
Mit dem Widget Previewer
gibt es auch in Flutter eine Vorschau wie @Preview in Compose:
Eine Funktion, die ein Widget zurückgibt, bekommt die Annotation
@Preview aus package:flutter/widget_previews.dart. In VS Code
erscheint die Vorschau im Tab Flutter Widget Preview in der
Seitenleiste. In dieser Lektion verwenden wir Hot Reload.
Code: schritt-01 ·
Änderungen
Layouts: Column, Row, Container
Widgets werden mit Layout-Widgets angeordnet.
Column: untereinanderRow: nebeneinanderStack: übereinanderContainer: Hintergrund, Rahmen, Abstände um ein KindSizedBox: fester Abstand,Spacer: freier Platz
Die Verschachtelung im Code entspricht der Verschachtelung auf dem Bildschirm.
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Lörrach', style: TextStyle(fontSize: 28)),
Text('⛅', style: TextStyle(fontSize: 72)),
Text('14 °C', style: TextStyle(fontSize: 48)),
SizedBox(height: 16),
Row(children: [Text('Wind: 8 km/h'), Spacer(), Text('Teilweise bewölkt')]),
],
)Dieser Code steckt in einem neuen Widget CurrentWeatherCard, ebenfalls
in ui/weather_screen.dart. WeatherScreen gibt nur noch
const CurrentWeatherCard() zurück. Die vollständige Datei folgt im
nächsten Abschnitt, wenn die Karte noch Abstände und Hintergrund
bekommt.
Column und Row zentrieren ihre Kinder quer zur Hauptachse
standardmäßig. Entlang der Hauptachse nimmt eine Column so viel Platz
ein wie möglich; mainAxisSize: MainAxisSize.min macht sie so klein wie
ihr Inhalt.
Spacer() nimmt den gesamten freien Platz in der Row ein und schiebt
so den zweiten Text an den rechten Rand, wie Spacer(Modifier.weight(1f))
in Compose.
Größen sind logische Pixel, das entspricht dp. Eine eigene Einheit
für Schrift wie sp gibt es nicht: Flutter skaliert Text automatisch mit
der Schriftgröße aus den Systemeinstellungen.
Im Repository steht die Row über mehrere Zeilen, so wie sie der
Formatierer (dart format) anordnet. Seit Dart 3.7 entscheidet er
selbst nach der Zeilenlänge, ob ein Aufruf in eine oder mehrere Zeilen
kommt, und setzt oder entfernt die abschließenden Kommas passend.
Speichern genügt, von Hand formatieren muss man nichts.
Abstände und Hintergrund
Einen Modifier gibt es nicht. Abstände und Hintergrund sind Eigenschaften von Container oder eigene Widgets wie Padding.
Container(
width: double.infinity,
margin: const EdgeInsets.all(16), // außen
padding: const EdgeInsets.all(24), // innen
decoration: BoxDecoration(
color: const Color(0xFFBBDEFB),
borderRadius: BorderRadius.circular(24),
),
child: const Column(/* von oben */),
)Anders als bei Modifiern in Compose spielt die Reihenfolge der
Argumente keine Rolle: margin ist immer außen, padding immer innen.
Dafür wird der Baum tiefer, sobald mehrere Widgets ineinander stecken,
z. B. Padding(padding: …, child: Center(child: …)).
width: double.infinity entspricht fillMaxWidth(): so breit wie der
verfügbare Platz.
Am besten mit Hot Reload ausprobieren: margin oder padding ändern
oder entfernen und den Unterschied ansehen.
Code: lib/ui/weather_screen.dart (schritt-02)
lib/ui/weather_screen.dart (schritt-02)import 'package:flutter/material.dart';
class WeatherScreen extends StatelessWidget {
const WeatherScreen({super.key});
@override
Widget build(BuildContext context) {
return const CurrentWeatherCard();
}
}
class CurrentWeatherCard extends StatelessWidget {
const CurrentWeatherCard({super.key});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: const Color(0xFFBBDEFB),
borderRadius: BorderRadius.circular(24),
),
child: const Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Lörrach', style: TextStyle(fontSize: 28)),
Text('⛅', style: TextStyle(fontSize: 72)),
Text('14 °C', style: TextStyle(fontSize: 48)),
SizedBox(height: 16),
Row(
children: [
Text('Wind: 8 km/h'),
Spacer(),
Text('Teilweise bewölkt'),
],
),
],
),
);
}
}Code: schritt-02 ·
Änderungen
Datenmodell mit Klassen
Statt fester Texte bekommen Widgets nun Daten über den Konstruktor.
class City {
const City({
required this.name,
required this.latitude,
required this.longitude,
});
final String name;
final double latitude;
final double longitude;
}Der Code oben ist die vollständige Datei model/city.dart im neuen
Ordner model.
Eine data class wie in Kotlin gibt es in Dart nicht. Wir schreiben
eine normale Klasse mit final-Feldern und einem const-Konstruktor.
{ … } macht die Parameter zu benannten Parametern, required
macht sie zu Pflichtangaben. Aufgerufen wird mit Namen, wie in Kotlin:
City(name: 'Lörrach', latitude: 47.6156, longitude: 7.6614).
equals und copy erzeugt Dart nicht automatisch; wir brauchen sie hier
auch nicht.
CurrentWeather ist genauso aufgebaut.
Für die Wetterlage gibt es ein enum, Werte in lowerCamelCase:
enum WeatherCondition {
clear,
partlyCloudy,
cloudy,
fog,
rain,
snow,
thunderstorm,
}
class CurrentWeather {
const CurrentWeather({
required this.temperature,
required this.windSpeed,
required this.condition,
});
final double temperature;
final double windSpeed;
final WeatherCondition condition;
}Die Koordinaten in City brauchen wir erst in Schritt 9 für die API.
Die Fake-Daten kommen in den neuen Ordner data:
import '../model/city.dart';
import '../model/current_weather.dart';
const fakeCities = [
City(name: 'Lörrach', latitude: 47.6156, longitude: 7.6614),
City(name: 'Freiburg', latitude: 47.9990, longitude: 7.8421),
City(name: 'Basel', latitude: 47.5596, longitude: 7.5886),
];
const fakeCurrentWeather = {
'Lörrach': CurrentWeather(
temperature: 14.2,
windSpeed: 8.5,
condition: WeatherCondition.partlyCloudy,
),
'Freiburg': CurrentWeather(
temperature: 15.0,
windSpeed: 6.1,
condition: WeatherCondition.clear,
),
'Basel': CurrentWeather(
temperature: 13.6,
windSpeed: 11.2,
condition: WeatherCondition.rain,
),
};Listen ([…]) und Maps ({…}) haben in Dart eigene Literale. Die Typen
List<City> und Map<String, CurrentWeather> leitet Dart ab.
Dart für Kotlin-Kenner: Vieles ist ähnlich, z. B. Null-Safety mit
String?, ?. und !, Extension Methods oder String-Templates mit
'$name' bzw. '${city.name}'. Unterschiede: Semikolons sind Pflicht,
der Typ steht vor dem Namen (final String name), Strings meist in
einfachen Anführungszeichen, und _ am Namensanfang macht etwas
privat (statt private).
Widgets sollten Daten über den Konstruktor erhalten und sie nicht eigenständig holen.
class CurrentWeatherCard extends StatelessWidget {
const CurrentWeatherCard({super.key, required this.city, required this.weather});
final City city;
final CurrentWeather weather;
@override
Widget build(BuildContext context) {
// Container und Column wie bisher, die Texte jetzt aus den Daten:
// Text(city.name), Text(weather.condition.emoji), ...
}
}Weil die Texte jetzt aus Variablen kommen, entfällt das const vor der
Column; nur die festen TextStyles bleiben const.
Die Anzeige der Wetterlage ist eine Extension mit einem
switch-Ausdruck. Sie gehört zur UI, nicht ins Datenmodell:
import '../model/current_weather.dart';
extension WeatherConditionUi on WeatherCondition {
String get emoji => switch (this) {
WeatherCondition.clear => '☀️',
WeatherCondition.partlyCloudy => '⛅',
WeatherCondition.cloudy => '☁️',
WeatherCondition.fog => '🌫️',
WeatherCondition.rain => '🌧️',
WeatherCondition.snow => '❄️',
WeatherCondition.thunderstorm => '⛈️',
};
String get label => switch (this) {
WeatherCondition.clear => 'Sonnig',
WeatherCondition.partlyCloudy => 'Teilweise bewölkt',
WeatherCondition.cloudy => 'Bewölkt',
WeatherCondition.fog => 'Nebel',
WeatherCondition.rain => 'Regen',
WeatherCondition.snow => 'Schnee',
WeatherCondition.thunderstorm => 'Gewitter',
};
}Wie when in Kotlin muss der switch-Ausdruck alle Werte des enum
abdecken. Kommt ein neuer Wert hinzu, meldet der Analyzer jede Stelle,
die angepasst werden muss. emoji ist ein Getter (get), deshalb steht
beim Aufruf keine Klammer: weather.condition.emoji. Nach demselben
Muster liefert label den Text („Sonnig“, „Regen“ …).
Auch WeatherScreen bekommt city und weather über den Konstruktor
und reicht sie an die Karte weiter:
Code: lib/ui/weather_screen.dart (schritt-03)
lib/ui/weather_screen.dart (schritt-03)import 'package:flutter/material.dart';
import '../model/city.dart';
import '../model/current_weather.dart';
import 'weather_condition_ui.dart';
class WeatherScreen extends StatelessWidget {
const WeatherScreen({super.key, required this.city, required this.weather});
final City city;
final CurrentWeather weather;
@override
Widget build(BuildContext context) {
return CurrentWeatherCard(city: city, weather: weather);
}
}
class CurrentWeatherCard extends StatelessWidget {
const CurrentWeatherCard({
super.key,
required this.city,
required this.weather,
});
final City city;
final CurrentWeather weather;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: const Color(0xFFBBDEFB),
borderRadius: BorderRadius.circular(24),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(city.name, style: const TextStyle(fontSize: 28)),
Text(weather.condition.emoji, style: const TextStyle(fontSize: 72)),
Text(
'${weather.temperature.round()} °C',
style: const TextStyle(fontSize: 48),
),
const SizedBox(height: 16),
Row(
children: [
Text('Wind: ${weather.windSpeed.round()} km/h'),
const Spacer(),
Text(weather.condition.label),
],
),
],
),
);
}
}round() rundet auf eine ganze Zahl, '${…}' setzt einen Ausdruck in
einen String ein.
main.dart importiert die Fake-Daten und übergibt sie:
Code: lib/main.dart (schritt-03)
lib/main.dart (schritt-03)import 'package:flutter/material.dart';
import 'data/fake_data.dart';
import 'ui/weather_screen.dart';
void main() {
runApp(const WeatherApp());
}
class WeatherApp extends StatelessWidget {
const WeatherApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Wetter',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),
home: Scaffold(
body: SafeArea(
child: WeatherScreen(
city: fakeCities.first,
weather: fakeCurrentWeather[fakeCities.first.name]!,
),
),
),
);
}
}Das const vor Scaffold entfällt, weil WeatherScreen jetzt Werte
erhält, die erst zur Laufzeit feststehen.
Der Zugriff auf eine Map liefert CurrentWeather?, weil es den
Schlüssel vielleicht nicht gibt. ! sagt: Hier ist der Wert sicher da.
Fehlt er doch, stürzt die App ab, wie mit getValue in Kotlin.
Code: schritt-03 ·
Änderungen
Scaffold und AppBar
Flutter bringt die Widgets von Material Design 3 mit. Scaffold ist das Grundgerüst eines Screens, z. B. mit einer AppBar.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(city.name)),
body: SingleChildScrollView(
child: CurrentWeatherCard(city: city, weather: weather),
),
);
}Das Scaffold wandert aus main.dart in WeatherScreen: Der Code oben
ersetzt die build-Methode von WeatherScreen in
ui/weather_screen.dart, CurrentWeatherCard bleibt unverändert.
main.dart setzt WeatherScreen jetzt direkt als home ein, ohne
Scaffold und SafeArea. Dies ersetzt den bisherigen Wert von home:
home: WeatherScreen(
city: fakeCities.first,
weather: fakeCurrentWeather[fakeCities.first.name]!,
),Ein innerPadding wie in Compose gibt es nicht: Das Scaffold legt
body automatisch unter die AppBar, und die AppBar berücksichtigt die
Statusleiste selbst. Für den oberen Rand ist SafeArea deshalb nicht
mehr nötig. Seitliche Aussparungen im Querformat schützt die AppBar
nicht; bei Inhalten bis an den Rand hilft dort weiterhin SafeArea.
SingleChildScrollView macht den Inhalt scrollbar, wenn er nicht auf
den Bildschirm passt. Im Querformat ist die Karte höher als der freie
Platz; ohne Scrollen zeigt Flutter einen gelb-schwarz gestreiften
Balken (Overflow). Anders als in Compose scrollt in Flutter nichts von
selbst, und zu große Inhalte werden nicht stillschweigend
abgeschnitten.
Die AppBar folgt den Konventionen der Plattform: Auf Android steht
der Titel links, auf iOS mittig.
Die Karte behält vorerst ihre feste Farbe und Schriftgrößen. Farben und
Schriften aus dem Theme, Card und Dark Mode kommen am Ende dazu (siehe
Material 3).
Code: schritt-04 ·
Änderungen
Übung: Karte gestalten
- Farben, Abstände und Schriftgrößen der Karte ändern, mit Hot Reload ausprobieren
- Bonus: neuer Parameter
highlighted, der die Karte mit einem Rahmen hervorhebt
Der Aufruf in WeatherScreen soll so aussehen:
CurrentWeatherCard(city: city, weather: weather, highlighted: true)Ohne highlighted soll die Karte aussehen wie bisher.
Tipps: Ein benannter Parameter ohne required braucht einen
Standardwert, z. B. this.highlighted = false. Einen Rahmen zeichnet die
BoxDecoration mit border: Border.all(…); null bedeutet kein Rahmen.
Lösung
class CurrentWeatherCard extends StatelessWidget {
const CurrentWeatherCard({
super.key,
required this.city,
required this.weather,
this.highlighted = false,
});
final City city;
final CurrentWeather weather;
final bool highlighted;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: const Color(0xFFBBDEFB),
borderRadius: BorderRadius.circular(24),
border: highlighted
? Border.all(color: const Color(0xFF1565C0), width: 3)
: null,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(city.name, style: const TextStyle(fontSize: 28)),
Text(weather.condition.emoji, style: const TextStyle(fontSize: 72)),
Text(
'${weather.temperature.round()} °C',
style: const TextStyle(fontSize: 48),
),
const SizedBox(height: 16),
Row(
children: [
Text('Wind: ${weather.windSpeed.round()} km/h'),
const Spacer(),
Text(weather.condition.label),
],
),
],
),
);
}
}Mit this.highlighted = false ist der Parameter optional, wie ein
Default-Wert in Kotlin. Das Feld bleibt final bool ohne ?, weil es
immer einen Wert hat.
Das Referenz-Repository enthält diese Änderung nicht. Die eingeklappten Dateien auf den folgenden Seiten überschreiben sie; wer sie behalten will, muss sie dort selbst wieder einbauen.
Weiterführendes Material
- Flutter einrichten
- Flutter-SDK manuell installieren
- Flutter für Android-Entwickler : Vergleich mit Compose und Views
- Layouts in Flutter
- Widget-Katalog
- Hot Reload
- Flutter DevTools : Widget Inspector, Debugging und Performance
- Dart-Sprachtour