Flutter: PopupMenuButton verwenden und Menüstrukturen
Zuletzt aktualisiert:
5 Min. Lesezeit

Mit PopupMenuButton bietet man sekundäre Aktionen an, ohne Platz auf dem Bildschirm zu verbrauchen: Der Nutzer tippt auf das Drei-Punkte-Symbol, ein kleines Menü öffnet sich, ein Eintrag wird gewählt, und das Menü schließt sich wieder. Wir verwenden es für Aktionen, die nicht in die AppBar passen, etwa „Einstellungen, Hilfe, Abmelden“, oder für die Optionen „Bearbeiten, Teilen, Löschen“ einer Listenzeile. Jeder Eintrag trägt einen value, und über diesen Wert erfahren wir in onSelected, welcher Eintrag gewählt wurde.
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
PopupMenuButton<String>(
onSelected: (String value) {
debugPrint('Gewählt: $value');
},
itemBuilder: (BuildContext context) => const [
PopupMenuItem<String>(
value: 'edit',
child: Text('Bearbeiten'),
),
PopupMenuItem<String>(
value: 'delete',
child: Text('Löschen'),
),
],
)Ohne icon oder child wird der Button mit dem „Mehr“-Symbol der jeweiligen Plattform gezeichnet. Am häufigsten sitzt er in der actions-Liste einer AppBar.
Verwendung mit Enum
Bei String-Werten rutscht ein Tippfehler ('delte' statt 'delete') am Compiler vorbei, und das Menü tut stillschweigend nichts. Ein Enum als Werttyp ist die gesündere Wahl:
enum MenuAction { edit, delete, share }
PopupMenuButton<MenuAction>(
onSelected: (MenuAction action) {
switch (action) {
case MenuAction.edit:
_editItem();
case MenuAction.delete:
_deleteItem();
case MenuAction.share:
_shareItem();
}
},
itemBuilder: (context) => const [
PopupMenuItem(value: MenuAction.edit, child: Text('Bearbeiten')),
PopupMenuItem(value: MenuAction.delete, child: Text('Löschen')),
PopupMenuItem(value: MenuAction.share, child: Text('Teilen')),
],
)In Dart 3 braucht ein case mit Rumpf kein break mehr. Noch besser: Kommt ein neuer Wert ins Enum, erinnert der Compiler an das fehlende case. Was Enums sonst noch können, zeigt der Beitrag zu Enums in Dart.
Menüeinträge mit Symbolen
PopupMenuItem<String>(
value: 'settings',
child: Row(
children: const [
Icon(Icons.settings),
SizedBox(width: 10),
Text('Einstellungen'),
],
),
)PopupMenuDivider und CheckedPopupMenuItem
Eine Trennlinie gruppiert Einträge. Die gefährliche Aktion (Löschen) vom Rest abzusetzen ist eine gute Gewohnheit:
itemBuilder: (context) => const [
PopupMenuItem(value: 'edit', child: Text('Bearbeiten')),
PopupMenuItem(value: 'copy', child: Text('Kopieren')),
PopupMenuDivider(), // Trennlinie
PopupMenuItem(value: 'delete', child: Text('Löschen')),
]Zeigt das Menü keine Aktion, sondern eine Einstellung (zum Beispiel die Sortierung), verwendet man CheckedPopupMenuItem, das die aktuelle Wahl mit einem Haken markiert:
CheckedPopupMenuItem(
value: SortBy.date,
checked: _sortBy == SortBy.date,
child: const Text('Nach Datum'),
)Wichtige Eigenschaften
| Eigenschaft | Beschreibung |
|---|---|
onSelected |
Wird aufgerufen, wenn ein Eintrag gewählt wird |
onCanceled |
Wird aufgerufen, wenn das Menü ohne Auswahl geschlossen wird |
itemBuilder |
Erzeugt die Menüeinträge |
icon |
Symbol des Menü-Buttons |
child |
Eigenes Button-Widget |
tooltip |
Text bei langem Druck und für Screenreader |
position |
PopupMenuPosition.over oder under |
offset |
Versatz der Menüposition |
shape |
Form des Menüs |
color |
Hintergrundfarbe des Menüs |
enabled |
Menü aktiv oder inaktiv |
Verwendung mit eigenem Button
PopupMenuButton<String>(
position: PopupMenuPosition.under, // Menü unterhalb des Buttons öffnen
onSelected: (value) {},
itemBuilder: (context) => const [
PopupMenuItem(value: 'week', child: Text('Diese Woche')),
PopupMenuItem(value: 'month', child: Text('Dieser Monat')),
],
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Text('Menü', style: TextStyle(color: Colors.white)),
Icon(Icons.arrow_drop_down, color: Colors.white),
],
),
),
)Wann verwenden – und wann nicht?
Ein Popup-Menü versteckt Aktionen. Das ist Stärke und Schwäche zugleich: Der Bildschirm wird ruhiger, aber der Nutzer erfährt womöglich nie, dass es die Aktion gibt.
- Verwenden für sekundäre, selten genutzte Aktionen, für Optionen einer einzelnen Listenzeile und für alles jenseits des dritten Symbols in einer AppBar.
- Nicht verwenden für die Hauptaktion eines Bildschirms. „Neue Notiz“ darf nicht im Menü verschwinden, sondern verdient einen sichtbaren Button oder einen FAB. Bei nur ein bis zwei Aktionen sind einfache
IconButtons besser. - Nicht verwenden, um in einem Formular einen Wert auszuwählen. Muss der gewählte Wert sichtbar bleiben, ist der DropdownButton die richtige Komponente; ein Popup-Menü merkt sich keine Auswahl, es löst nur eine Aktion aus.
- Nicht verwenden für viele oder erklärungsbedürftige Optionen. Auf dem Smartphone bietet
showModalBottomSheetmehr Platz und liegt näher am Daumen (siehe BottomSheet-Beitrag). Wer auf dem Desktop Untermenüs braucht, findet inMenuAnchoraus Material 3 das passende Werkzeug.
Häufige Fehler
1. Eintrag ohne value
Symptom: Man tippt auf den Eintrag, das Menü schließt sich, aber onSelected wird nie aufgerufen.
Ursache: Ein Eintrag ohne value liefert null, und PopupMenuButton deutet das als „nichts ausgewählt“. Statt onSelected läuft onCanceled.
Lösung: Jedem PopupMenuItem einen value geben und den generischen Typ des Buttons ausschreiben (PopupMenuButton<MenuAction>).
2. Dialog oder Navigation in onTap
Symptom: Im Parameter onTap eines PopupMenuItem wird showDialog aufgerufen, und in manchen Flutter-Versionen blitzt der Dialog nur kurz auf und schließt sich sofort wieder.
Ursache: onTap läuft, während die Menü-Route geschlossen wird. In älteren Versionen nahm das pop, das das Menü schloss, den frisch geöffneten Dialog gleich mit.
Lösung: onTap nur für kleine Aufgaben ohne Context nutzen und Dialoge oder Seitenwechsel in onSelected erledigen. onSelected wird aufgerufen, nachdem die Menü-Route entfernt wurde, und ist damit unabhängig von der Version sicher.
3. child und icon gleichzeitig übergeben
Symptom: Die Assertion „You can only pass [child] or [icon], not both.“
Lösung: icon verwenden, wenn nur ein anderes Symbol gewünscht ist, und child, wenn man den ganzen Button selbst zeichnen will.
Mini-Szenario: Bearbeiten, Teilen, Löschen in einer Listenzeile
Stellen wir uns eine Notizliste vor. Jede Zeile endet mit einem Drei-Punkte-Menü; bei „Löschen“ wird zuerst nachgefragt und danach eine kurze Meldung gezeigt. Zuerst die Zeile:
enum NoteAction { edit, share, delete }
ListTile(
title: Text(note.title),
trailing: PopupMenuButton<NoteAction>(
tooltip: 'Optionen zur Notiz',
onSelected: (action) => _onNoteAction(action, note),
itemBuilder: (context) => const [
PopupMenuItem(value: NoteAction.edit, child: Text('Bearbeiten')),
PopupMenuItem(value: NoteAction.share, child: Text('Teilen')),
PopupMenuDivider(),
PopupMenuItem(value: NoteAction.delete, child: Text('Löschen')),
],
),
)Dann die Methode, die die Aktionen verarbeitet:
Future<void> _onNoteAction(NoteAction action, Note note) async {
switch (action) {
case NoteAction.edit:
await Navigator.push(
context,
MaterialPageRoute(builder: (_) => EditNotePage(note: note)),
);
case NoteAction.share:
// Teilen-Logik
break;
case NoteAction.delete:
final ok = await showDialog<bool>(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('Notiz löschen?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext, false),
child: const Text('Abbrechen'),
),
FilledButton(
onPressed: () => Navigator.pop(dialogContext, true),
child: const Text('Löschen'),
),
],
),
);
if (ok != true || !mounted) return;
setState(() => _notes.remove(note));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Notiz gelöscht')),
);
}
}Das Menü meldet nur die Absicht; die eigentliche Arbeit steckt in einer einzigen Methode. So lassen sich dieselben Aktionen morgen problemlos an eine Wischgeste oder einen Button auf der Detailseite hängen. Die Einzelheiten der Rückfrage stehen im Beitrag zum AlertDialog. Beachten Sie auch die mounted-Prüfung nach dem await: Der Nutzer könnte die Seite verlassen haben, während der Dialog offen war.
Häufig gestellte Fragen
Warum wird onSelected bei meinem PopupMenuButton nicht aufgerufen?
Sehr wahrscheinlich hat der gewählte PopupMenuItem keinen value. Ein Eintrag ohne Wert liefert null, und dann wird onCanceled statt onSelected aufgerufen.
Wie deaktiviere ich einen Menüeintrag?
Übergeben Sie dem PopupMenuItem enabled: false. Er wird blass dargestellt und lässt sich nicht antippen. Das ganze Menü deaktiviert der Parameter enabled des PopupMenuButton.
Wie öffne ich das Menü unterhalb des Buttons?
Standardmäßig öffnet sich das Menü über dem Button. Mit position: PopupMenuPosition.under erscheint es direkt darunter, und über offset lässt sich die Position feinjustieren.
Was ist der Unterschied zwischen PopupMenuButton und DropdownButton?
PopupMenuButton löst eine Aktion aus und speichert die Auswahl nicht. DropdownButton lässt einen Wert auswählen und zeigt ihn auf dem Button an – genau das, was Formulare brauchen.
Verwandte Artikel
Flutter: AppBar-Widget und Anpassung
AppBar in Flutter richtig einsetzen: title, leading, actions und bottom, der Farbwechsel beim Scrollen in Material 3 und Lösungen für häufige Fehler.
Flutter: BottomSheet und showModalBottomSheet
showModalBottomSheet in Flutter: Werte zurückgeben, isScrollControlled, useSafeArea, showDragHandle, Tastatur, DraggableScrollableSheet und persistente Sheets.
Flutter: SnackBar und SnackBarAction verwenden
SnackBar mit ScaffoldMessenger in Flutter: Rückgängig-Aktion, Floating-Stil, Warteschlangen-Problem und Context-Fehler nach await.