Flutter: DropdownButton verwenden und Eigenschaften
Zuletzt aktualisiert:
6 Min. Lesezeit

DropdownButton ist die platzsparendste Möglichkeit, den Nutzer genau einen Wert aus einer festen Liste wählen zu lassen; Android-Entwickler kennen das als Spinner. Geschlossen zeigt er nur den gewählten Wert, ein Tipp darauf öffnet die Liste. Wir verwenden ihn für Felder, deren Optionen vorab feststehen, etwa Kategorie, Stadt oder Währung. Weil kein Freitext möglich ist, verhindert er fehlerhafte Eingaben von vornherein.
Live-Demo
Sie können dieses Widget im interaktiven Beispiel unten ausprobieren:
💡 Falls das Beispiel oben nicht lädt, klicken Sie auf DartPad, um es in einem neuen Tab auszuführen.
Grundlegende Verwendung
DropdownButton hält keinen eigenen Zustand. Den gewählten Wert speichern wir selbst in einer Variablen und aktualisieren ihn in onChanged mit setState:
String? selectedValue;
DropdownButton<String>(
value: selectedValue,
hint: const Text('Bitte wählen'),
items: ['Option 1', 'Option 2', 'Option 3'].map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? newValue) {
setState(() {
selectedValue = newValue;
});
},
)selectedValue muss ein Feld der State-Klasse sein und keine lokale Variable in build. Sonst wird es bei jedem Neuaufbau wieder null, und die Auswahl erscheint nie. Solange der Wert null ist, wird hint angezeigt; ein künstlicher Eintrag „Bitte wählen“ in der Liste ist also überflüssig.
Wichtige Eigenschaften
| Eigenschaft | Beschreibung |
|---|---|
value |
Der ausgewählte Wert |
hint |
Widget, das ohne Auswahl angezeigt wird |
disabledHint |
Widget, das bei deaktiviertem Button angezeigt wird |
items |
Liste der DropdownMenuItem |
onChanged |
Wird bei einer Änderung aufgerufen; null deaktiviert den Button |
isExpanded |
Passt die Breite an das übergeordnete Widget an |
underline |
Widget für die Unterstreichung |
icon |
Symbol des Aufklapp-Pfeils |
dropdownColor |
Hintergrundfarbe der aufgeklappten Liste |
menuMaxHeight |
Maximale Höhe der aufgeklappten Liste |
DropdownButtonFormField
Innerhalb von Formularen sollte DropdownButtonFormField bevorzugt werden. Es nimmt eine InputDecoration entgegen, sieht also aus wie die TextFormFields daneben, und nimmt über validator an der Validierung teil:
DropdownButtonFormField<String>(
value: selectedValue,
decoration: const InputDecoration(
labelText: 'Kategorie',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.category),
),
items: categories.map((String category) {
return DropdownMenuItem<String>(
value: category,
child: Text(category),
);
}).toList(),
onChanged: (String? newValue) {
setState(() {
selectedValue = newValue;
});
},
validator: (value) {
if (value == null || value.isEmpty) {
return 'Bitte wählen Sie eine Kategorie';
}
return null;
},
)Wie die Validierung über einen GlobalKey<FormState> ausgelöst wird, erklärt der Beitrag zum Form-Widget. Ein kleiner Hinweis: Neuere Flutter-Versionen empfehlen für den Parameter value dieses Widgets stattdessen initialValue. Zeigt der Editor eine Deprecation-Warnung, genügt es, den Parameter umzubenennen.
Angepasste DropdownMenuItem
Da child ein beliebiges Widget sein darf, lassen sich Einträge mit Symbolen und Labels anreichern:
DropdownMenuItem<String>(
value: 'premium',
child: Row(
children: [
const Icon(Icons.star, color: Colors.amber),
const SizedBox(width: 10),
const Text('Premium-Mitgliedschaft'),
const SizedBox(width: 10),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(10),
),
child: const Text(
'Empfohlen',
style: TextStyle(color: Colors.white, fontSize: 10),
),
),
],
),
)Die Material-3-Alternative: DropdownMenu
Das mit Material 3 eingeführte DropdownMenu erledigt dieselbe Aufgabe im Look eines Textfelds. Der größte Unterschied: Man kann durch Tippen filtern.
DropdownMenu<String>(
label: const Text('Land'),
initialSelection: 'tr',
enableFilter: true,
requestFocusOnTap: true, // Tastatur auch auf dem Smartphone öffnen
onSelected: (String? value) {
setState(() => _country = value);
},
dropdownMenuEntries: const [
DropdownMenuEntry(value: 'tr', label: 'Türkei'),
DropdownMenuEntry(value: 'de', label: 'Deutschland'),
DropdownMenuEntry(value: 'gb', label: 'Vereinigtes Königreich'),
],
)Die Einträge sind hier keine Widgets, sondern DropdownMenuEntry-Objekte mit einem label-Text, und gefiltert wird über diesen Text. Sobald es mehr als etwa zehn Optionen gibt und Nutzer lieber tippen als scrollen, bietet DropdownMenu das bessere Erlebnis. Für ein klassisches Formularfeld mit fünf oder sechs Optionen bleibt DropdownButtonFormField die praktischste Lösung.
Wann verwenden – und wann nicht?
Ein Dropdown versteckt seine Optionen; der Nutzer muss einmal tippen, nur um zu sehen, was es gibt. An diesem Preis sollte man die Entscheidung ausrichten:
- Verwenden bei ungefähr 4 bis 15 sich gegenseitig ausschließenden Optionen und in Formularen mit wenig Platz.
- Nicht verwenden bei zwei oder drei Optionen. Ein
SegmentedButtonoderRadioListTilezeigt alle auf einmal und ist schneller und klarer. Solche Eingabekomponenten behandelt der Beitrag zu Input-Widgets. - Nicht verwenden bei sehr langen Listen. Durch Hunderte Einträge zu scrollen ist eine Qual; besser ist ein filterbares
DropdownMenuoder eine eigene Auswahlseite mit Suchfeld. - Nicht verwenden für Mehrfachauswahl. Ein Dropdown hält einen Wert; für mehrere eignen sich
FilterChips oder eine Liste mit Checkboxen. - Nicht verwenden, um Aktionen auszulösen. Befehle wie „Bearbeiten, Löschen“ sind Aktionen und keine Werte; sie gehören in einen PopupMenuButton.
Häufige Fehler
1. Der value ist nicht in den items enthalten
Symptom: Eine der bekanntesten Assertions in Flutter: „There should be exactly one item with [DropdownButton]'s value: … Either zero or 2 or more [DropdownMenuItem]s were detected with the same value“.
Die Ursache tritt in drei Varianten auf. Der Anfangswert steht nicht in der Liste (etwa value: 'Bitte wählen'). Die Liste hat sich später geändert, und die alte Auswahl ist nicht mehr enthalten. Oder zwei Einträge haben denselben value.
Lösung: Mit null starten und hint verwenden; die Auswahl zurücksetzen, sobald sich die Liste ändert; auf eindeutige Werte achten.
2. Eigene Klasse als value ohne ==
Symptom: Dieselbe Assertion, obwohl die Liste korrekt aussieht. Meist tritt sie auf, nachdem die Daten erneut von der API geladen wurden.
Ursache: In Dart sind zwei Objekte standardmäßig nicht gleich, auch wenn ihr Inhalt übereinstimmt. Das City(34, 'Istanbul') aus der neu geladenen Liste passt nicht zu dem alten Objekt, das wir noch halten.
Lösung: == und hashCode in der Klasse überschreiben oder gleich die id als value verwenden:
class City {
const City(this.id, this.name);
final int id;
final String name;
@override
bool operator ==(Object other) => other is City && other.id == id;
@override
int get hashCode => id.hashCode;
}3. Lange Texte laufen über
Symptom: Bei langem ausgewähltem Text erscheint „A RenderFlex overflowed by … pixels on the right“ samt gelb-schwarzer Streifen.
Lösung: isExpanded: true setzen und dem Text des Eintrags overflow: TextOverflow.ellipsis geben. Sitzt das Dropdown in einer Row, reicht isExpanded allein nicht, weil eine Row ihrem Kind unbegrenzte Breite gibt; das Dropdown muss zusätzlich in ein Expanded gepackt werden.
4. Das Dropdown ist grau und nicht antippbar
Symptom: Der Button wird blass gezeichnet, öffnet sich nicht und meldet keinen Fehler.
Ursache: onChanged ist null oder die items-Liste ist leer. Flutter betrachtet den Button dann als deaktiviert.
Lösung: War das keine Absicht, onChanged angeben. War es Absicht, dem Nutzer mit disabledHint sagen, warum.
Mini-Szenario: Stadtteile abhängig von der Stadt
Stellen wir uns ein Adressformular vor: Zuerst wird die Stadt gewählt, und die Liste der Stadtteile füllt sich passend dazu. Genau hier passiert Fehler Nummer eins am häufigsten. Nach der Wahl von Berlin und Kreuzberg stellt der Nutzer die Stadt auf Hamburg um; das Stadtteil-Feld hat noch den value 'Kreuzberg', aber in der neuen Liste gibt es diesen Eintrag nicht.
final _formKey = GlobalKey<FormState>();
final Map<String, List<String>> _districts = {
'Berlin': ['Kreuzberg', 'Mitte', 'Pankow'],
'Hamburg': ['Altona', 'Eimsbüttel'],
'München': ['Schwabing', 'Sendling'],
};
String? _city;
String? _district;
@override
Widget build(BuildContext context) {
final districtList = _districts[_city] ?? const <String>[];
return Form(
key: _formKey,
child: Column(
children: [
DropdownButtonFormField<String>(
value: _city,
isExpanded: true,
decoration: const InputDecoration(labelText: 'Stadt'),
items: _districts.keys
.map((c) => DropdownMenuItem(value: c, child: Text(c)))
.toList(),
onChanged: (value) => setState(() {
_city = value;
_district = null; // Stadt geändert, alter Stadtteil ungültig
}),
validator: (v) => v == null ? 'Stadt wählen' : null,
),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
key: ValueKey(_city), // Feld pro Stadt komplett neu aufbauen
value: _district,
isExpanded: true,
decoration: const InputDecoration(labelText: 'Stadtteil'),
disabledHint: const Text('Zuerst Stadt wählen'),
items: districtList
.map((d) => DropdownMenuItem(value: d, child: Text(d)))
.toList(),
onChanged: _city == null
? null
: (value) => setState(() => _district = value),
validator: (v) => v == null ? 'Stadtteil wählen' : null,
),
const SizedBox(height: 24),
FilledButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// _city und _district können gespeichert werden
}
},
child: const Text('Speichern'),
),
],
),
);
}Zwei Details lösen das Problem. Mit _district = null beim Wechsel der Stadt wird der ungültige Wert entfernt. Der key: ValueKey(_city) am Stadtteil-Feld sorgt dafür, dass das Formularfeld auch den intern gehaltenen alten Wert verwirft: Wechselt die Stadt, behandelt Flutter das Feld als neues Widget und baut es von Grund auf neu. Solange keine Stadt gewählt ist, bleibt das Stadtteil-Feld durch onChanged: null deaktiviert, und disabledHint sagt dem Nutzer, was zu tun ist.
Häufig gestellte Fragen
Wie setze ich beim DropdownButton einen Anfangswert?
Übergeben Sie dem Parameter value den Wert eines Eintrags aus items. Soll anfangs nichts ausgewählt sein, bleibt value null, und hint zeigt einen Hinweistext.
Warum erscheint „There should be exactly one item with [DropdownButton]'s value“?
Der übergebene value passt zu keinem oder zu mehr als einem Eintrag in items. Prüfen Sie den Anfangswert, ob die Auswahl nach einer Listenänderung zurückgesetzt wird und ob eigene Klassen == überschreiben.
Was ist der Unterschied zwischen DropdownButton und DropdownMenu?
DropdownButton nimmt Widgets als Einträge und verhält sich wie eine klassische Aufklappliste. Das DropdownMenu aus Material 3 sieht aus wie ein Textfeld, verwendet DropdownMenuEntry und erlaubt mit enableFilter das Filtern durch Tippen.
Wie bringe ich ein Dropdown auf volle Breite?
Setzen Sie isExpanded: true; das Dropdown füllt dann die Breite des Eltern-Widgets, und der Pfeil rückt an den rechten Rand. DropdownButtonFormField besitzt denselben Parameter.
Verwandte Artikel
Flutter: Helles und dunkles Theme mit ThemeData
Ein Flutter-Theme mit ThemeData aufbauen: ColorScheme.fromSeed, helles und dunkles Theme, ThemeMode, Theme.of, TextTheme, Komponenten-Themes, ThemeExtension.
Flutter: BottomSheet und showModalBottomSheet
showModalBottomSheet in Flutter: Werte zurückgeben, isScrollControlled, useSafeArea, showDragHandle, Tastatur, DraggableScrollableSheet und persistente Sheets.
Flutter: BottomNavigationBar im Vergleich zu NavigationBar
Von BottomNavigationBar zur Material-3-NavigationBar: Zuordnungstabelle, labelBehavior, NavigationBarThemeData, Entscheidungshilfe und adaptive Shell.