Flutter: Platz ausfüllen mit dem Expanded-Widget
Zuletzt aktualisiert:
7 Min. Lesezeit

Expanded vergrößert ein Kind in einer Row oder Column so, dass es den gesamten Platz einnimmt, den die Geschwister übrig lassen. Ein Text, der die Lücke zwischen Icon und Button füllt, eine Liste, die die Höhe über einer unteren Leiste bekommt, zwei Panels, die sich den Bildschirm 2:1 teilen: Das alles ist die Aufgabe dieses Widgets. Dieser Beitrag konzentriert sich auf Expanded selbst: die flex-Rechnung, Verschachtelung, warum es nur in Row, Column und Flex funktioniert und die drei Fehler, die Sie in der Konsole am häufigsten sehen. Wann Sie statt Expanded besser Flexible oder Spacer wählen, und fertige Layout-Rezepte mit allen dreien, erkläre ich separat im Vergleich von Expanded, Flexible und Spacer; hier geht es nur um das Verhalten von Expanded selbst.
Live-Demo: Beispiele zum Expanded-Widget
Probieren Sie Expanded und den Einsatz von flex interaktiv aus:
💡 Falls das Beispiel oben nicht lädt, klicken Sie auf DartPad, um es in einem neuen Tab auszuführen.
Grundlegende Verwendung
In einer Row arbeitet Expanded auf der horizontalen Achse, in einer Column auf der vertikalen. Kinder mit fester Größe werden zuerst gemessen, was übrig bleibt, bekommt Expanded:
Row(
children: [
Container(width: 50, color: Colors.red),
Expanded(
child: Container(color: Colors.blue, child: const Text('Ich bin gewachsen')),
),
Container(width: 50, color: Colors.green),
],
)Der blaue Container nimmt die gesamte Breite, die nach Rot und Grün übrig bleibt. Verschieben Sie denselben Code in eine Column und ersetzen width durch height, füllt die blaue Fläche die verbleibende Höhe. Expanded greift nicht in die Querachse ein: Die Höhe eines Expanded in einer Row bestimmt die crossAxisAlignment-Regel der Row.
Die flex-Rechnung
Gibt es mehrere Expanded, wird der Restplatz im Verhältnis der flex-Werte aufgeteilt. Die Rechnung hat zwei Schritte: Zuerst werden die festen Kinder abgezogen, dann wird der Rest durch die flex-Summe geteilt und jedes Kind bekommt seinen Anteil.
Row(
children: [
const SizedBox(width: 100, child: Text('Fest')),
Expanded(
flex: 1,
child: Container(color: Colors.red, child: const Text('1 Anteil')),
),
Expanded(
flex: 3,
child: Container(color: Colors.blue, child: const Text('3 Anteile')),
),
],
)Bei einem 500 Pixel breiten Bildschirm gehen zuerst 100 Pixel feste Breite ab; die restlichen 400 Pixel werden in 1+3 = 4 Anteile geteilt: Rot bekommt 100, Blau 300 Pixel. Ein verbreiteter Irrtum ist, flex: 1 und flex: 3 als ein Viertel und drei Viertel der Gesamtbreite zu lesen; das Verhältnis gilt immer für den Restplatz. Ohne flex ist der Wert 1, zwei nackte Expanded teilen sich den Platz also je zur Hälfte.
Flex-Werte haben nur relativ zueinander eine Bedeutung: flex: 2 und flex: 4 liefern dasselbe Ergebnis wie flex: 1 und flex: 2. Verwenden Sie der Lesbarkeit wegen die kleinsten ganzen Zahlen.
Was Expanded wirklich ist
Expanded ist die Kurzform von Flexible(fit: FlexFit.tight). tight bedeutet, dem Kind zu sagen: „Sei genau so groß.“ Selbst wenn das Kind lieber kleiner wäre, füllt es den gesamten Restplatz. FlexFit.loose, der Standard von Flexible, sagt dagegen: „Sei höchstens so groß.“ Diese Unterscheidung erklärt auch, warum ein Container mit width: 100 in einem Expanded seine Breite ignoriert: Die enge Constraint überschreibt den Wunsch des Containers.
Eine weitere Folge: Expanded zieht eine Row selbst dann auf volle Breite, wenn sie mainAxisSize: MainAxisSize.min hat, weil es den gesamten Restplatz fordert. Dieses Zusammenspiel behandle ich im Beitrag zu mainAxisSize.
Verschachteltes Expanded
Das Kind eines Expanded kann eine Row oder Column sein, die wiederum eigene Expanded enthält. Jedes Expanded schaut nur auf das Flex-Widget direkt über sich:
Row(
children: [
Expanded(
flex: 2,
child: Column(
children: [
Expanded(child: Container(color: Colors.red)),
Expanded(child: Container(color: Colors.blue)),
],
),
),
Expanded(
child: Container(color: Colors.green),
),
],
)Die äußere Row teilt die Breite 2:1; die linke Column teilt die Höhe ihres Anteils in zwei gleiche Teile. Die inneren Expanded haben mit der äußeren Row nichts zu tun. Das funktioniert, weil die äußere Row vom Bildschirm eine begrenzte Höhe bekommt und sie an die Column weitergibt. Wäre die Höhe unbegrenzt, würden die inneren Expanded einen Fehler werfen; dazu gleich mehr.
Wann verwenden – und wann nicht?
Verwenden Sie Expanded in einer Row oder Column immer dann, wenn ein Kind nach der Regel „was übrig bleibt“ dimensioniert werden soll: ein langer Text, der den Platz zwischen Icon und Button füllt, ein Inhaltsbereich, der die gesamte Höhe über einer unteren Leiste bekommt, Spalten, die sich den Bildschirm anteilig teilen.
In diesen Fällen brauchen Sie ein anderes Werkzeug:
- Soll das Kind seine eigene Größe behalten und nur schrumpfen, wenn es nicht passt, nehmen Sie
Flexible(Standardloose). In Layouts, die sich an den Inhalt schmiegen, etwa einer Chat-Blase, zieht Expanded den Text grundlos auf volle Breite. - Brauchen Sie nur Leerraum, nehmen Sie
Spacer. Statt eines leerenExpanded(child: SizedBox())erledigtSpacer(flex: 2)dieselbe Aufgabe lesbar; Details im Spacer-Beitrag. - Wollen Sie volle Breite außerhalb von Row oder Column, nehmen Sie
SizedBox(width: double.infinity)odercrossAxisAlignment: CrossAxisAlignment.stretchan der Column. Expanded wirft in einem Nicht-Flex-Elternteil einen Fehler. - Brauchen Sie einen bestimmten Prozentsatz des Elternteils, nehmen Sie
FractionallySizedBox(widthFactor: 0.6). Expanded bildet Verhältnisse nur relativ zu seinen Geschwistern. - In einem scrollbaren Bereich hat die Hauptachse keine Grenze, dort kann Expanded nicht arbeiten. Geben Sie eine feste Höhe oder machen Sie die Liste selbst scrollbar.
Häufige Fehler
1. Expanded in einer Column in einer ScrollView
SingleChildScrollView(
child: Column(
children: [
const Text('Titel'),
Expanded(child: ListView(children: const [Text('Zeile')])), // Fehler
],
),
)Symptom: RenderFlex children have non-zero flex but incoming height constraints are unbounded. Die ScrollView bietet der Column unendliche Höhe, und der „Rest“ von unendlich lässt sich nicht berechnen. Es gibt drei Auswege: Expanded entfernen und der inneren Liste eine feste Höhe wie SizedBox(height: 300) geben; der inneren Liste shrinkWrap: true und physics: NeverScrollableScrollPhysics() geben und das äußere Scrollen nutzen; oder die Column aus der ScrollView nehmen und nur die Liste scrollbar machen. Bei langen Listen ist der dritte Weg der effizienteste; die Scroll-Optionen finden Sie im Beitrag zu ScrollView und Listen-Widgets.
2. Ein anderes Widget zwischen Expanded und Row setzen
Row(
children: [
Padding(
padding: const EdgeInsets.all(8),
child: Expanded(child: Text('Text')), // Fehler
),
],
)Symptom: Ein langer Fehler, der mit Incorrect use of ParentDataWidget. beginnt. Expanded ist ein „Parent-Data“-Widget; es schreibt seine flex-Information direkt in die darüberliegende Row. Steht dazwischen ein Padding, Container oder Center, landet die Information an der falschen Stelle. Drehen Sie die Reihenfolge um: zuerst Expanded, darin Padding.
3. Feste Breite innerhalb von Expanded
Symptom: Sie haben Expanded(child: Container(width: 120, ...)) geschrieben, und der Container breitet sich trotzdem über den gesamten Restplatz aus. Die enge Constraint überschreibt die Breite des Containers. Wollen Sie eine feste Breite, entfernen Sie Expanded; wollen Sie „höchstens 120“, nehmen Sie Flexible mit ConstrainedBox(constraints: BoxConstraints(maxWidth: 120)).
4. Expanded bei langem Text vergessen
Symptom: Ein gelb-schwarzer Streifen am rechten Rand und A RenderFlex overflowed by ... pixels on the right. Eine Row begrenzt nicht, wie viel Platz ein Text verlangen darf. Packen Sie den Text in Expanded und ergänzen Sie overflow: TextOverflow.ellipsis:
Row(
children: [
const Icon(Icons.label),
const SizedBox(width: 8),
Expanded(
child: Text(
'Ein Beschriftungstext, der zu lang für eine Zeile ist',
overflow: TextOverflow.ellipsis,
),
),
],
)Mini-Szenario: Bezahlbildschirm
Stellen Sie sich einen Bezahlbildschirm vor: oben scrollbare Formularfelder, unten die immer sichtbaren Buttons „Zurück“ und „Zahlung abschließen“. Das Expanded in der Column dehnt das Formular über die restliche Höhe; die beiden Expanded in der Row teilen die Buttons 1:2:
class CheckoutScreen extends StatelessWidget {
const CheckoutScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Bezahlen')),
body: Column(
children: [
Expanded(
child: ListView(
padding: const EdgeInsets.all(16),
children: const [
TextField(decoration: InputDecoration(labelText: 'Kartennummer')),
SizedBox(height: 12),
TextField(decoration: InputDecoration(labelText: 'Gültig bis')),
SizedBox(height: 12),
TextField(decoration: InputDecoration(labelText: 'CVC')),
],
),
),
SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => Navigator.pop(context),
child: const Text('Zurück'),
),
),
const SizedBox(width: 12),
Expanded(
flex: 2,
child: FilledButton(
onPressed: () {},
child: const Text('Zahlung abschließen'),
),
),
],
),
),
),
],
),
);
}
}Das funktioniert, weil Scaffold dem Body eine begrenzte Höhe gibt; die Column misst zuerst die unteren Buttons und überlässt den Rest dem Expanded. Öffnet sich die Tastatur, schrumpft der Body, der Bereich des Expanded wird automatisch kleiner und die Liste bleibt scrollbar. Hätten Sie dieselbe Liste in eine SingleChildScrollView gesetzt, wären Sie beim ersten Fehler gelandet. In der Button-Zeile ist SizedBox(width: 12) fest und wird vor der Rechnung abgezogen; die restliche Breite wird in drei Anteile geteilt, und „Zahlung abschließen“ bekommt zwei davon.
Häufig gestellte Fragen
Was ist der Unterschied zwischen Expanded und Flexible?
Expanded ist dasselbe wie Flexible(fit: FlexFit.tight): Das Kind muss den gesamten Restplatz ausfüllen. Der Standardwert loose von Flexible erlaubt dem Kind, kleiner zu bleiben.
Warum funktioniert Expanded nicht in einem Container?
Expanded kann nur direktes Kind von Row, Column oder Flex sein. In jedem anderen Widget erhalten Sie Incorrect use of ParentDataWidget; für volle Breite verwenden Sie SizedBox(width: double.infinity).
Wie behebe ich „incoming height constraints are unbounded“?
Das Expanded steckt in einer Column, deren Elternteil auf der Hauptachse unbegrenzte Constraints liefert, meist eine SingleChildScrollView oder ListView. Entfernen Sie das Expanded und geben Sie eine feste Höhe, oder nehmen Sie die Column aus dem Scroll-Bereich.
Ist der flex-Wert ein Prozentsatz des Gesamtplatzes?
Nein. Geschwister mit fester Größe werden zuerst abgezogen, dann wird der Restplatz durch die flex-Summe geteilt. Enthält die Row ein festes Kind von 100 Pixeln, bekommen flex: 1 und flex: 1 je die Hälfte des Rests, nicht des Ganzen.
Verwandte Artikel
Flutter: Flexible Abstände mit dem Spacer-Widget
Was in Spacer steckt, Abstände mit flex gewichten, der Unterschied zu SizedBox und mainAxisAlignment und warum Spacer in scrollbaren Bereichen scheitert.
Flutter: Platz und Ausrichtung mit mainAxisSize steuern
max und min bei mainAxisSize, der Einfluss der Constraints, warum mainAxisAlignment scheinbar nichts tut, und Rezepte für Dialoge, Sheets und Button-Gruppen.
Flutter: Expanded, Flexible und Spacer im Vergleich
Der Unterschied zwischen Expanded, Flexible und Spacer, loose und tight fit, dazu vier Rezepte: Werkzeugleiste, Kartenfußzeile, Formular, geteilter Screen.