Flutter: Card-Widget und Einsatz im Design
Zuletzt aktualisiert:
6 Min. Lesezeit

Card ist das Material-Design-Widget, das zusammengehörige Informationen mit leichtem Schatten und abgerundeten Ecken auf einer Fläche bündelt. Wir setzen es ein, um Inhalte vom Hintergrund abzuheben, die „für sich ein Ganzes“ sind: ein Produkt, eine Nachricht, eine Nutzerübersicht. Eine eigene Größe hat es nicht; es richtet sich nach dem Kind, das Sie hineinlegen – deshalb sieht man es fast immer zusammen mit Padding, Column oder ListTile.
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
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Text('Karteninhalt'),
),
)Eine Card gibt ihrem Inhalt von sich aus keinen Innenabstand; ohne Padding klebt der Text am Kartenrand. Nach außen lässt sie dagegen standardmäßig auf jeder Seite einen kleinen margin, sodass sich untereinander angeordnete Karten nicht berühren.
Wichtige Eigenschaften
| Eigenschaft | Beschreibung |
|---|---|
elevation |
Höhe des Schattens |
shadowColor |
Farbe des Schattens |
shape |
Form der Karte (Eckenradius, Rahmen) |
color |
Hintergrundfarbe |
margin |
Außenabstand |
clipBehavior |
Ob der Inhalt auf die Kartenform zugeschnitten wird |
Kartentypen in Material 3
Material 3 definiert drei Kartentypen, und Flutter bringt für jeden einen fertigen Konstruktor mit:
// Elevated: leichter Schatten, die Standardkarte
Card(child: ...)
// Filled: ohne Schatten, hebt sich durch die Füllfarbe ab
Card.filled(child: ...)
// Outlined: ohne Schatten, dünner Rahmen
Card.outlined(child: ...)Welche Variante passt, hängt vom Hintergrund und von der Bedeutung der Karte ab. Die Karte mit Schatten zieht am meisten Aufmerksamkeit auf sich; in vollen Listen, in denen viele Karten aufeinanderfolgen, wirken Card.outlined oder Card.filled ruhiger. Weil sie ihre Farben aus dem ColorScheme des Themes beziehen, sehen sie im hellen wie im dunklen Theme ohne Zusatzarbeit richtig aus.
Angepasste Card
Reichen die fertigen Typen nicht aus, erlauben shape, elevation und color die Feinabstimmung:
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
color: Theme.of(context).colorScheme.surfaceContainerHigh,
margin: const EdgeInsets.all(8),
child: ...
)Möchten Sie den Rahmen selbst definieren, verwenden Sie side innerhalb von shape. Das ist die handgeschriebene Form von Card.outlined und hilft, wenn Rahmenfarbe oder -stärke geändert werden sollen:
Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: Theme.of(context).colorScheme.outlineVariant),
),
child: ...
)Card mit Bild und clipBehavior
Eine Card hat abgerundete Ecken, ihr Standardwert für clipBehavior ist jedoch Clip.none – die Karte schneidet ihr Kind also nicht zu. Liegt oben ein Bild in voller Breite, ragen dessen spitze Ecken über die runden Ecken der Karte hinaus. Clip.antiAlias behebt das:
Card(
clipBehavior: Clip.antiAlias, // Bildecken auf die Kartenform zuschneiden
child: Column(
children: [
Image.network(
'https://example.com/image.jpg',
height: 150,
width: double.infinity,
fit: BoxFit.cover,
),
const Padding(
padding: EdgeInsets.all(16),
child: Text('Kartentitel'),
),
],
),
)Zuschneiden kostet Rechenzeit, deshalb lässt Flutter es standardmäßig ausgeschaltet. Es genügt, es bei den Karten einzuschalten, die es brauchen. Wie Sie Bilder ins Projekt aufnehmen, steht im Beitrag zu Assets und Image.asset.
Verwendung mit ListTile
Für einzeilige Infokarten liefert ListTile mit dem wenigsten Code das sauberste Ergebnis:
Card(
child: ListTile(
leading: const CircleAvatar(child: Icon(Icons.person)),
title: const Text('Benutzername'),
subtitle: const Text('Untertitel'),
trailing: const Icon(Icons.chevron_right),
onTap: () {},
),
)Antippbare Card mit InkWell
Card besitzt keinen eigenen onTap-Parameter. Um sie antippbar zu machen, legen wir ein InkWell hinein; da eine Card eine Material-Fläche ist, erscheint der Ripple direkt auf der Karte:
Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: () {
print('Karte angetippt');
},
child: const Padding(
padding: EdgeInsets.all(16),
child: Text('Antippbare Karte'),
),
),
)Auch hier ist clipBehavior wichtig: Ohne ihn breitet sich der Ripple rechteckig aus und läuft über die runden Ecken der Karte hinaus. InkWell und GestureDetector vergleiche ich im GestureDetector-Beitrag.
Wann verwenden – und wann nicht?
Card ist die richtige Wahl, wenn ein Bildschirm mehrere unabhängige Inhaltsblöcke enthält, die sich voneinander abheben sollen: ein Produktraster, ein News-Feed, Übersichtskacheln in einem Dashboard.
In diesen Fällen passt etwas anderes besser:
- Einfache, gleichförmige Listenzeilen: Auf Bildschirmen wie Einstellungen oder einer Kontaktliste macht eine Karte um jede Zeile die Oberfläche schwer.
ListTilemit einemDividerdazwischen genügt. - Verläufe, eigene Schatten oder ein Look außerhalb von Material: Der Parameter
colorvon Card nimmt genau eine Farbe. Für einen Verlauf brauchen Sie einenContainermitBoxDecoration. - Eine Fläche, die die ganze Seite oder einen großen Teil davon bedeckt: Eine Karte soll Inhalt vom Hintergrund trennen; ist alles eine Karte, hebt sich nichts mehr ab. Aus demselben Grund sollte man Karten in Karten vermeiden.
Häufige Fehler
1. Die Bildecken ragen aus der Karte heraus
Symptom: Die unteren Ecken der Karte sind rund, die oberen mit dem Bild spitz. Ursache ist, wie oben beschrieben, der Standardwert Clip.none; clipBehavior: Clip.antiAlias an der Card löst das Problem. Ein zusätzliches ClipRRect um das Bild ist nicht nötig.
2. Die Card von außen in einen GestureDetector packen
Symptom: Die Karte reagiert auf Tippen, zeigt aber keinerlei visuelle Reaktion – der Nutzer weiß nicht, ob seine Berührung erkannt wurde. Ein InkWell außen herum hilft ebenfalls nicht; der Ripple landet dann hinter der Karte. Die richtige Reihenfolge lautet Card > InkWell > Inhalt.
3. Die Farbe fest verdrahten
Symptom: Eine Karte, die im hellen Theme gut aussieht, bleibt im dunklen Theme grellweiß, und der helle Text darin ist nicht mehr lesbar. Ursache ist ein fester Wert wie color: Colors.white. Lassen Sie die Farbe weg oder wählen Sie sie aus Theme.of(context).colorScheme, dann stimmt die Karte in beiden Themes. Farbrollen und den Aufbau von hellem und dunklem Theme behandelt der Beitrag zu Theme und ThemeData.
4. Karte mit unbegrenzter Breite in einer Row
Symptom: Eine Karte mit einem ListTile in einer Row führt zu einem Layoutfehler wegen unendlicher Breite. Eine Row setzt ihren Kindern keine Breitengrenze, ein ListTile möchte aber alle verfügbare Breite. Packen Sie die Karte in ein Expanded oder geben Sie ihr mit SizedBox eine Breite. Dasselbe gilt für horizontal scrollende Listen.
Mini-Szenario: eine Produktkarte
Denken Sie an das Produktraster einer Shopping-App. Jede Karte zeigt ein Bild, einen Titel mit höchstens zwei Zeilen und einen Preis; ein Tipp auf die Karte öffnet das Produktdetail. Weil wir diese Struktur immer wieder brauchen, lagern wir sie in ein eigenes Widget aus:
class ProductCard extends StatelessWidget {
const ProductCard({
super.key,
required this.title,
required this.price,
required this.imageUrl,
required this.onTap,
});
final String title;
final String price;
final String imageUrl;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: Image.network(imageUrl, fit: BoxFit.cover),
),
Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: textTheme.titleMedium,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(price, style: textTheme.bodyMedium),
],
),
),
],
),
),
);
}
}Dahinter stecken ein paar bewusste Entscheidungen. Statt einer festen Bildhöhe verwenden wir AspectRatio; das Bild behält sein Seitenverhältnis, egal ob die Karte schmal oder breit ist. Der Titel ist mit maxLines und ellipsis begrenzt, weil ein langer Produktname sonst die Höhe der Karten im Raster durcheinanderbringt. Die Textstile kommen aus dem Theme, statt von Hand geschrieben zu werden. Noch ein Detail: Da der Ripple auf die Material-Fläche gezeichnet wird, ist er über dem Bild nicht sichtbar, nur im Textbereich. Soll er auch über dem Bild erscheinen, kann Ink.image an die Stelle von Image.network treten.
In einem GridView eingesetzt, ergibt diese Karte das fertige Produktraster. Warum es sich lohnt, wiederkehrende UI-Bausteine in eigene Klassen auszulagern, erkläre ich ausführlich im Beitrag Eigene Widgets erstellen.
Häufig gestellte Fragen
Was ist der Unterschied zwischen Card und Container?
Card ist eine fertige Fläche mit Schatten, Eckenradius und Theme-Farben nach Material Design und stellt den Material-Vorfahren bereit, den InkWell braucht. Container ist eine Allzweck-Box; wählen Sie ihn, wenn Sie einen Verlauf oder eine völlig eigene Dekoration brauchen.
Wie mache ich eine Card antippbar?
Card hat keinen onTap-Parameter. Packen Sie den Inhalt in ein InkWell und geben Sie der Card clipBehavior: Clip.antiAlias, damit der Ripple den Ecken der Karte folgt. Besteht der Inhalt aus einer einzigen Zeile, funktioniert auch der onTap-Parameter von ListTile.
Wann verwende ich Card.filled und Card.outlined?
Beide sind schattenlose Kartentypen aus Material 3. Auf dichten Bildschirmen mit vielen Karten oder dort, wo eine dezente Abgrenzung vom Hintergrund genügt, wirken sie ruhiger als die Standardkarte mit Schatten.
Warum sind die Ecken des Bildes in meiner Card nicht abgerundet?
Standardmäßig schneidet eine Card ihr Kind nicht zu, weil clipBehavior auf Clip.none steht. Sobald Sie der Card clipBehavior: Clip.antiAlias geben, wird das Bild auf die Form der Karte zugeschnitten.
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: AlertDialog verwenden und anpassen
AlertDialog in Flutter: Bestätigung, Eingabe und adaptive Dialoge – mit Lösungen für context.mounted, StatefulBuilder und Overflow.