Flutter: FloatingActionButton (FAB) verwenden
Zuletzt aktualisiert:
6 Min. Lesezeit

Der FloatingActionButton (FAB) ist der Button, der über dem Inhalt „schwebt“ und für die eine wichtigste Aktion eines Screens steht: „Neue Notiz“ in einer Notiz-App, „Schreiben“ in einem Mail-Client, „Mein Standort“ in einer Karte. Er ist das Erste, worauf das Auge fällt; deshalb ist die Frage, welche Aufgabe der FAB bekommt, wichtiger als die Frage nach seiner Farbe. In diesem Beitrag geht es neben den Grundlagen um die Material-3-Größen, die Positionierung, das Ausblenden beim Scrollen und um den heroTag-Fehler, über den fast jeder einmal stolpert.
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
Der FAB gehört in den Parameter floatingActionButton des Scaffold. Das Scaffold positioniert ihn, schiebt ihn nach oben, wenn eine SnackBar erscheint, und animiert ihn bei Seitenwechseln. Verwenden Sie deshalb diesen Parameter, statt den Button von Hand in einen Stack zu setzen.
Scaffold(
appBar: AppBar(title: const Text('FAB-Beispiel')),
body: const Center(child: Text('Hallo Flutter')),
floatingActionButton: FloatingActionButton(
onPressed: () {
debugPrint('FAB angetippt!');
},
tooltip: 'Hinzufügen', // Bei langem Drücken und für Screenreader sichtbar
child: const Icon(Icons.add),
),
)In Material 3 ist die Standardfarbe des FAB das primaryContainer des Themes, das Symbol bekommt onPrimaryContainer. Ist das Theme sauber aufgesetzt, erhalten Sie also ohne eine einzige Farbangabe einen passenden Button.
Größen: small, regular, large und extended
Mit Material 3 gibt es den FAB in drei Größen und in einer Variante mit Text, jeweils als eigener Konstruktor:
FloatingActionButton.small(onPressed: () {}, child: const Icon(Icons.edit)),
FloatingActionButton(onPressed: () {}, child: const Icon(Icons.add)),
FloatingActionButton.large(onPressed: () {}, child: const Icon(Icons.navigation)),
FloatingActionButton.extended(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('Neue Aufgabe'),
),small ist für eine untergeordnete Aktion gedacht, large für eine Aktion, die praktisch der einzige Zweck des Screens ist (der Auslöser in einer Kamera-App), und extended für Fälle, in denen das Symbol allein nicht eindeutig ist. Das ältere mini: true funktioniert weiterhin, in Material-3-Projekten ist FloatingActionButton.small aber die bevorzugte Schreibweise.
Anpassung
Farbe, Schatten und Form lassen sich direkt am Widget ändern:
FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.purple, // Buttonfarbe
foregroundColor: Colors.white, // Symbolfarbe
elevation: 8.0, // Schattentiefe
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: const Icon(Icons.download),
)Sollen alle FABs der App gleich aussehen, wiederholen Sie diese Angaben nicht, sondern übergeben ein FloatingActionButtonThemeData an das Feld floatingActionButtonTheme in ThemeData. Das spart Wiederholung, und die Variante für das dunkle Theme liegt an einer Stelle.
Positionierung (floatingActionButtonLocation)
Die Position wird über den Parameter floatingActionButtonLocation des Scaffold festgelegt. Standard ist endFloat, also unten rechts.
Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
),
bottomNavigationBar: BottomAppBar(
shape: const CircularNotchedRectangle(), // Schneidet eine Kerbe für den FAB
child: Row(
children: [
IconButton(onPressed: () {}, icon: const Icon(Icons.menu)),
const Spacer(),
IconButton(onPressed: () {}, icon: const Icon(Icons.search)),
],
),
),
)Häufig verwendete Werte:
| Wert | Position |
|---|---|
endFloat |
Unten rechts (Standard) |
centerFloat |
Unten mittig |
startFloat |
Unten links |
endDocked, centerDocked |
In eine BottomAppBar eingebettet, mit Kerbe |
endContained |
Innerhalb der BottomAppBar, ohne Kerbe (Material-3-Stil) |
endTop, miniEndTop |
An der Unterkante der AppBar |
Die Kerbe (docked) war ein Markenzeichen von Material 2; in Material 3 wirkt die Position endContained, bei der der FAB ohne Kerbe in der BottomAppBar sitzt, zeitgemäßer.
FAB beim Scrollen ausblenden
Bei langen Listen ist der übliche Weg, damit der FAB keinen Inhalt verdeckt: beim Herunterscrollen ausblenden, beim Hochscrollen wieder einblenden. Die Scrollrichtung lesen wir mit NotificationListener<UserScrollNotification>, die Sichtbarkeit animieren wir mit AnimatedScale:
import 'package:flutter/rendering.dart'; // für ScrollDirection
bool _fabVisible = true;
bool _onScroll(UserScrollNotification n) {
if (n.direction == ScrollDirection.reverse && _fabVisible) {
setState(() => _fabVisible = false); // Nach unten gescrollt: ausblenden
} else if (n.direction == ScrollDirection.forward && !_fabVisible) {
setState(() => _fabVisible = true); // Nach oben gescrollt: einblenden
}
return false; // Benachrichtigung weiter nach oben durchreichen
}
// in build:
floatingActionButton: AnimatedScale(
scale: _fabVisible ? 1 : 0,
duration: const Duration(milliseconds: 200),
child: FloatingActionButton(onPressed: _add, child: const Icon(Icons.add)),
),ScrollDirection.reverse klingt verkehrt: Wenn der Inhalt nach oben wandert (der Nutzer den Finger nach oben zieht), wächst der Scroll-Offset, und genau das nennt Flutter „reverse“.
Wann verwenden – und wann nicht?
- Verwenden, wenn der Screen eine einzige primäre, aufbauende Aktion hat (erstellen, hinzufügen, schreiben, senden). Daher stammt die Regel „ein FAB pro Screen“; bei zwei FABs ist nicht mehr klar, welche Aktion die Hauptaktion ist.
- Nicht verwenden für destruktive Aktionen (löschen, abmelden). Die gehören in ein Menü oder in den Listeneintrag selbst, abgesichert durch einen AlertDialog.
- Nicht verwenden, wenn mehrere gleichrangige Aktionen existieren. Der richtige Ort ist die
actions-Liste derAppBaroder ein PopupMenuButton. - Nicht verwenden für das Ergebnis eines Formulars. Der „Absenden“-Button einer Registrierung gehört in den Ablauf, unter die Felder; ein schwebender Button ist dort schwerer zu finden.
- Nicht verwenden, wenn der Screen bereits eine
BottomNavigationBaroder ein hohes Bottom Sheet hat, mit dem der FAB kollidieren würde. Dann in eineBottomAppBarmitendContainedverschieben oder auf den FAB verzichten.
Häufige Fehler
1. Zwei FABs auf einem Screen: der heroTag-Konflikt
Symptom: Beim Seitenwechsel stürzt die App auf den roten Fehlerbildschirm: „There are multiple heroes that share the same tag within a subtree.“
Ursache: Jeder FAB umhüllt sich selbst mit einem Hero, und alle tragen denselben Standard-heroTag. Bei zwei FABs auf einem Screen (oder in getrennten Scaffolds innerhalb eines IndexedStack) weiß Flutter nicht, welchen es fliegen lassen soll. Den Mechanismus und die typischen Hero-Fehler erklärt der Beitrag zu Hero-Animationen im Detail.
Lösung: Jedem FAB einen eigenen heroTag geben oder die Animation mit null abschalten:
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton.small(
heroTag: 'refresh',
onPressed: () {},
child: const Icon(Icons.refresh),
),
const SizedBox(height: 12),
FloatingActionButton(
heroTag: null, // Schaltet die Hero-Animation komplett ab
onPressed: () {},
child: const Icon(Icons.add),
),
],
)2. Der FAB verdeckt den letzten Listeneintrag
Symptom: Ganz unten in der Liste liegt die rechte Seite der letzten Zeile unter dem FAB; Button oder Text lassen sich nicht antippen.
Lösung: Am Ende der Liste Platz lassen. EdgeInsets.only(bottom: 88) im Parameter padding der ListView genügt; das Ausblenden beim Scrollen von oben ist eine sinnvolle Ergänzung.
3. Der FAB wirkt blass und reagiert nicht
Symptom: Der Button ist grau, Tippen bewirkt nichts.
Ursache: onPressed ist null. Wie beim ElevatedButton deaktiviert ein null-Callback auch den FAB. Meist steckt eine Bedingung wie onPressed: _isLoading ? null : _save dahinter, bei der das Lade-Flag nie zurückgesetzt wird.
Lösung: Sicherstellen, dass das Flag auf jedem Pfad zurückgesetzt wird; setState(() => _isLoading = false) in einem try/finally ist der sicherste Ort.
4. Sie erwarten einen Kreis und bekommen abgerundete Ecken
Symptom: Nach dem Umstieg auf Material 3 ist der FAB „eckig“.
Ursache: In der Designsprache von Material 3 ist die Standardform des FAB ein Quadrat mit abgerundeten Ecken. Das ist kein Fehler, sondern der neue Standard.
Lösung: Wer den Kreis zurückmöchte, übergibt shape: const CircleBorder() oder setzt das app-weit über floatingActionButtonTheme.
Mini-Szenario: Ein Erinnerungs-Screen
Stellen Sie sich eine Liste von Erinnerungen vor. Der Screen hat eine Aktion, das Hinzufügen: Ein Tipp auf den FAB öffnet einen Date Picker, das gewählte Datum landet in der Liste, und eine Meldung mit „Rückgängig“ erscheint. Während die Liste scrollt, verschwindet der FAB.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class RemindersScreen extends StatefulWidget {
const RemindersScreen({super.key});
@override
State<RemindersScreen> createState() => _RemindersScreenState();
}
class _RemindersScreenState extends State<RemindersScreen> {
final List<DateTime> _reminders = [];
bool _fabVisible = true;
bool _onScroll(UserScrollNotification n) {
if (n.direction == ScrollDirection.reverse && _fabVisible) {
setState(() => _fabVisible = false);
} else if (n.direction == ScrollDirection.forward && !_fabVisible) {
setState(() => _fabVisible = true);
}
return false;
}
Future<void> _addReminder() async {
final today = DateTime.now();
final date = await showDatePicker(
context: context,
initialDate: today,
firstDate: today,
lastDate: DateTime(today.year + 1),
);
if (date == null || !mounted) return;
setState(() => _reminders.insert(0, date));
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(
content: const Text('Erinnerung hinzugefügt'),
action: SnackBarAction(
label: 'Rückgängig',
onPressed: () => setState(() => _reminders.remove(date)),
),
),
);
}
@override
Widget build(BuildContext context) {
final loc = MaterialLocalizations.of(context);
return Scaffold(
appBar: AppBar(title: const Text('Erinnerungen')),
body: NotificationListener<UserScrollNotification>(
onNotification: _onScroll,
child: ListView.builder(
padding: const EdgeInsets.only(bottom: 88), // Letzte Zeile nicht unter dem FAB
itemCount: _reminders.length,
itemBuilder: (_, i) => ListTile(
leading: const Icon(Icons.alarm),
title: Text(loc.formatFullDate(_reminders[i])),
),
),
),
floatingActionButton: AnimatedScale(
scale: _fabVisible ? 1 : 0,
duration: const Duration(milliseconds: 200),
child: FloatingActionButton.extended(
onPressed: _addReminder,
icon: const Icon(Icons.add_alarm),
label: const Text('Erinnerung hinzufügen'),
),
),
);
}
}Drei Entscheidungen stecken hier drin. Erstens ist der Button extended: Ein nacktes „+“ beantwortet nicht die Frage „Erinnerung oder Notiz?“, die Beschriftung schon. Zweitens die Prüfung auf mounted nach dem Date Picker; showDatePicker ist ein Dialog, die Seite kann also während des await geschlossen worden sein, wie im Beitrag zum DatePicker erklärt. Drittens kommt das Feedback über eine SnackBar statt über einen Dialog: Hinzufügen lässt sich rückgängig machen, es gibt keinen Grund, den Nutzer zu unterbrechen. Der Aufruf hideCurrentSnackBar verhindert, dass sich Meldungen stauen, wenn mehrere Erinnerungen nacheinander angelegt werden.
Häufig gestellte Fragen
Warum erhalte ich „There are multiple heroes that share the same tag“?
Enthält ein Screen mehr als einen FloatingActionButton, tragen alle denselben Standard-heroTag, und beim Seitenwechsel weiß das Hero-System nicht, welchen es animieren soll. Geben Sie jedem FAB einen eigenen heroTag oder schalten Sie die Animation mit heroTag: null ab.
Darf ein Screen mehrere FABs haben?
Technisch ja, die Designrichtlinie sieht aber eine primäre Aktion pro Screen vor. Ist eine zweite Aktion nötig, kennzeichnen Sie sie mit FloatingActionButton.small als untergeordnet oder verschieben sie in die AppBar.
Wie blende ich den FAB beim Scrollen aus?
Die Liste in einen NotificationListener<UserScrollNotification> packen, anhand des Feldes direction ein Sichtbarkeits-Flag führen und den FAB in ein AnimatedScale oder AnimatedSlide setzen, das von diesem Flag gesteuert wird.
Wann ist FloatingActionButton.extended die bessere Wahl?
Wenn das Symbol allein mehrdeutig ist oder der sichtbare Name der Aktion die Nutzung erleichtert. Bei langem Text oder schmalem Bildschirm bleibt ein normaler FAB mit tooltip aufgeräumter.
Verwandte Artikel
Flutter: BottomSheet und showModalBottomSheet
showModalBottomSheet in Flutter: Werte zurückgeben, isScrollControlled, useSafeArea, showDragHandle, Tastatur, DraggableScrollableSheet und persistente Sheets.
Flutter: Card-Widget und Einsatz im Design
Card in Flutter: Material-3-Varianten Card.filled und Card.outlined, clipBehavior bei Bildern, antippbare Karten mit InkWell und typische Fehler.
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.