Flutter: Feine Ausrichtung mit dem Align-Widget
Zuletzt aktualisiert:
7 Min. Lesezeit

Align platziert ein einzelnes Kind an einem von Ihnen gewählten Punkt innerhalb des Bereichs, den es einnimmt: die obere rechte Ecke, die untere Mitte oder eine Zwischenposition wie „siebzig Prozent nach rechts, etwas über der Mitte". Center ist in Wirklichkeit ein auf alignment: Alignment.center festgelegtes Align; Align geht über die neun Voreinstellungen hinaus und bietet ein Koordinatensystem von -1 bis 1. In diesem Beitrag erkläre ich dieses Koordinatensystem, die Alternative FractionalOffset, wie widthFactor/heightFactor die eigene Größe von Align verändern und wie Sie im Stack zwischen Align und Positioned wählen.
Live-Demo: Beispiele zum Align-Widget
Sie können im interaktiven Beispiel unten verschiedene Alignment-Werte ausprobieren:
💡 Falls das Beispiel oben nicht lädt, klicken Sie auf DartPad, um es in einem neuen Tab auszuführen.
Grundlegende Verwendung
Container(
width: 300,
height: 200,
color: Colors.grey.shade200,
child: Align(
alignment: Alignment.topRight,
child: Icon(Icons.close, color: Colors.red),
),
)Align tut zwei Dinge: Zuerst bestimmt es seine eigene Größe (hier füllt es die 300x200 großen Fläche des Containers), dann positioniert es sein Kind innerhalb dieser Fläche gemäß alignment. Das Kind behält seine natürliche Größe; Align vergrößert es nie.
Das Koordinatensystem von Alignment
Alignment(x, y) nimmt zwei Zahlen, beide zwischen -1 und 1. Bei x steht -1 für die linke Kante, 0 für die Mitte und 1 für die rechte Kante; bei y steht -1 für oben, 0 für die Mitte und 1 für unten. Die neun vordefinierten Konstanten sind Kurzformen dieser Werte:
Alignment.topLeft // Alignment(-1, -1)
Alignment.topCenter // Alignment( 0, -1)
Alignment.topRight // Alignment( 1, -1)
Alignment.centerLeft // Alignment(-1, 0)
Alignment.center // Alignment( 0, 0)
Alignment.centerRight // Alignment( 1, 0)
Alignment.bottomLeft // Alignment(-1, 1)
Alignment.bottomCenter // Alignment( 0, 1)
Alignment.bottomRight // Alignment( 1, 1)Auch Zwischenwerte sind gültig: Alignment(0.5, 0) setzt das Kind horizontal genau zwischen Mitte und rechte Kante, Alignment(0, 0.8) ergibt eine Position nahe am unteren Rand, ohne ihn zu berühren. Der Wert ist ein Anteil des Raums, der nach Abzug der Kindgröße übrig bleibt; deshalb bedeutet 1 immer „bündig an der Kante", egal wie viele Pixel das Kind misst.
Den Bereich zu verlassen ist nicht verboten: Alignment(1.5, 0) schiebt das Kind teilweise rechts hinaus. In einer Schiebeanimation kann das nützlich sein, aber solange der Parent nicht beschneidet (clipBehavior), wird der überstehende Teil über benachbarte Widgets gezeichnet.
Für Sprachen von rechts nach links spiegeln sich Konstanten wie AlignmentDirectional.topStart und centerEnd anhand der Directionality; soll ein Schließen-Button immer dort sitzen, wo der Text endet, verwenden Sie diese statt des physischen topRight.
FractionalOffset: von 0 bis 1
Wenn Sie lieber von 0 bis 1 denken, gibt es FractionalOffset(dx, dy): (0, 0) ist oben links, (0.5, 0.5) die Mitte und (1, 1) unten rechts. Steht in einer Designdatei „30 % von links", schreiben Sie direkt FractionalOffset(0.3, 0), ohne das in Alignment(-0.4, -1) umzurechnen. Beide sind vom Typ AlignmentGeometry; der Parameter alignment akzeptiert beide.
Die eigene Größe von Align: widthFactor und heightFactor
Die Größe von Align folgt drei Regeln:
- Begrenzt der Parent die Achse, nimmt Align die größtmögliche Größe an.
- Ist der Parent auf dieser Achse unbegrenzt (die horizontale Achse einer Row, die vertikale Achse einer Column), schrumpft Align auf sein Kind.
- Ist
widthFactoroderheightFactorgesetzt, wird Align auf dieser Achse zum entsprechenden Vielfachen der Kindgröße und setzt Regel 1 außer Kraft.
Align(
alignment: Alignment.topLeft,
widthFactor: 2.0, // Breite von Align = Kindbreite x 2
heightFactor: 1.5, // Höhe von Align = Kindhöhe x 1.5
child: SizedBox(width: 50, height: 50, child: ColoredBox(color: Colors.blue)),
)Dieses Align misst 100x75 und wächst nicht, egal wie groß der Parent ist. Man nutzt das, um rund um ein Widget Platz proportional zum Kind zu lassen oder die Hälfte einer Box zu zeigen. Diese Parameter brauchen Sie selten, aber die Regeln 1 und 2 gelten für jedes Align, das Sie schreiben; die meisten Fehler unten entstehen daraus.
Align innerhalb einer Column
Eine Column gibt ihren Kindern begrenzte Breite (die Breite der Column) und unbegrenzte Höhe. Ein Align in einer Column ist deshalb so breit wie die Column und so hoch wie sein Kind, was es zum saubersten Weg macht, ein einzelnes Kind nach rechts oder links zu schieben:
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const TextField(decoration: InputDecoration(labelText: 'Passwort')),
Align(
alignment: Alignment.centerRight,
child: TextButton(onPressed: () {}, child: const Text('Passwort vergessen?')),
),
],
)crossAxisAlignment wirkt auf alle Kinder; Align nur auf das eine, das es umschließt. Wie Platz und stretch zusammenspielen, beschreibe ich ausführlicher im Beitrag zu mainAxisSize.
Align oder Positioned im Stack?
Die nicht positionierten Kinder eines Stack werden gemäß Stack.alignment platziert; mit Align geben Sie jedem Kind eine eigene Position. Positioned arbeitet mit Pixelwerten wie top und right; wie ein Stack seine Größe bestimmt und die ganze Positioned-Familie behandelt der Beitrag zu Stack und Positioned. So entscheiden Sie:
- Ist die Position proportional („obere rechte Ecke", „untere Mitte"), Align.
- Ist die Position in Pixeln („8 von oben, 8 von rechts") oder muss das Kind über den Stack hinausragen (ein Badge mit
clipBehavior: Clip.none), Positioned. - Hat der Stack keine feste Größe (Fehler 1 unten), ist Positioned sicherer, weil es nicht in die Größenberechnung des Stack eingeht.
Wann verwenden – und wann nicht?
Greifen Sie zu Align:
- Um ein einzelnes Kind an eine Ecke oder Kante seines Parents zu heften.
- Für Zwischenpositionen (
Alignment(0.7, -0.2)und Ähnliches); kein anderes Widget liefert das in einer Zeile. - Um ein Kind in einer Column nach rechts oder links zu schieben.
Wechseln Sie zu einem anderen Werkzeug:
- Für reines Zentrieren
Center; dasselbe, nur lesbarer. - Für Pixel-Offsets
Positioned(im Stack) oderTransform.translate(überall). - Wenn ohnehin ein Container da ist,
Container(alignment: ...); er baut intern dasselbe Align. - Um alle Kinder auf einmal auszurichten, die Parameter
mainAxisAlignmentundcrossAxisAlignmentvon Row und Column.
Häufige Fehler
1. Align im Stack bläht den ganzen Stack auf
Symptom: Das Badge, das an der Ecke eines Icons sitzen sollte, landet ganz rechts am Bildschirm. Ein Stack ist so groß wie sein größtes nicht positioniertes Kind. Weil Align in einem begrenzten Bereich „so groß wie möglich" wird, dehnt sich der Stack auf die volle Breite des Parents aus, und das Badge wandert in die Ecke dieser Breite.
// Falsch: das Align dehnt den Stack auf volle Breite
Stack(
children: [
const Icon(Icons.notifications, size: 32),
Align(alignment: Alignment.topRight, child: _Badge()),
],
)
// Richtig: dem Stack eine konkrete Größe geben...
SizedBox(
width: 40,
height: 40,
child: Stack(
children: [
const Align(alignment: Alignment.bottomLeft, child: Icon(Icons.notifications, size: 32)),
Align(alignment: Alignment.topRight, child: _Badge()),
],
),
)
// ...oder Positioned verwenden, das nicht in die Stack-Größe eingeht
Stack(
clipBehavior: Clip.none,
children: [
const Icon(Icons.notifications, size: 32),
Positioned(top: -4, right: -4, child: _Badge()),
],
)Material 3 bringt außerdem ein fertiges Badge-Widget mit; ziehen Sie es für Badges zuerst in Betracht.
2. Alignment(0.5, 0) als „50 % von links" lesen
Symptom: Das Kind sitzt weiter rechts als erwartet. Bei Alignment ist 0 die Mitte; 0.5 liegt auf halbem Weg zwischen Mitte und rechter Kante, also bei 75 % von links. Wenn Sie in Prozent denken, schreiben Sie FractionalOffset(0.5, 0).
3. Align in einer Row richtet nicht aus
Symptom: Sie haben Align(alignment: Alignment.centerRight, child: Text(...)) in eine Row gesetzt, und der Text rührt sich nicht. Row gibt unbegrenzte Breite; das Align schrumpft auf sein Kind und hat keinen Raum mehr, in dem es ausrichten könnte. Lösung: das Align in Expanded packen oder das mainAxisAlignment der Row verwenden. Warum Expanded nötig ist, erkläre ich im Beitrag zu Expanded.
Mini-Szenario: Bildkarte
Stellen Sie sich 200 Pixel hohe Bildkarten in einer Rezepte-App vor: ein Favoriten-Button oben rechts und ein Dauer-Label unten links. Da die Größe des Stack durch eine SizedBox feststeht, passt Align hier perfekt.
class RecipeImageCard extends StatelessWidget {
const RecipeImageCard({super.key, required this.imageUrl, required this.duration});
final String imageUrl;
final String duration;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 200,
child: Stack(
fit: StackFit.expand,
children: [
Image.network(imageUrl, fit: BoxFit.cover),
Align(
alignment: Alignment.topRight,
child: IconButton.filledTonal(
onPressed: () {},
icon: const Icon(Icons.favorite_border),
),
),
Align(
alignment: const Alignment(-0.9, 0.85),
child: Chip(
avatar: const Icon(Icons.schedule, size: 16),
label: Text(duration),
),
),
],
),
);
}
}StackFit.expand lässt das Bild die ganze Karte füllen; die beiden Aligns setzen ihre Kinder in die Ecken. Für das Label habe ich Alignment(-0.9, 0.85) verwendet: Es sitzt etwas innerhalb der Kante statt exakt in der Ecke, ganz ohne Padding-Wrapper. Ändert sich die Kartenbreite, bleiben die Positionen proportional erhalten; mit Positioned hätte ich Konstanten wie left: 12, bottom: 12 gebraucht, was ebenfalls eine gültige Wahl ist, aber wenn das Design proportionale Platzierung verlangt, bedeutet Align weniger Code.
Häufig gestellte Fragen
Was ist der Unterschied zwischen Align und Center?
Center ist ein auf alignment: Alignment.center festgelegtes Align; die Größenregeln und die Parameter widthFactor/heightFactor sind identisch. Wenn Sie nur zentrieren möchten, liest sich Center besser; für jede andere Position brauchen Sie Align.
Soll ich im Stack Align oder Positioned verwenden?
Align, wenn die Position proportional ist und der Stack eine feste Größe hat; Positioned, wenn Sie Pixelwerte brauchen, das Kind über den Stack hinausragen soll oder die Größe des Stack aus seinen Kindern abgeleitet wird. Positioned geht nicht in die Größenberechnung des Stack ein und verursacht deshalb nie unerwartetes Wachstum.
Sind Alignment und FractionalOffset dasselbe?
Sie leisten dasselbe auf unterschiedlichen Skalen: Alignment reicht von -1 bis 1 mit der Mitte bei 0; FractionalOffset reicht von 0 bis 1 mit der oberen linken Ecke bei 0. Beide lassen sich an alignment übergeben; wählen Sie das, was direkt zu Ihren Designwerten passt.
Warum ist Align größer als sein Kind?
Stellt der Parent einen begrenzten Bereich bereit, füllt Align ihn ganz aus; Ausrichtung ergibt nur Sinn, wenn es solchen freien Raum gibt. Möchten Sie Align auf die Kindgröße bringen, übergeben Sie widthFactor: 1 und heightFactor: 1 oder setzen es in einen unbegrenzten Parent.
Verwandte Artikel
Flutter: Das Center-Widget im Detail
Center und Align im Vergleich, warum Center den Platz ausfüllt, widthFactor, warum es in einer Column nicht vertikal zentriert, und typische Fehler.
Flutter: Widgets überlagern mit Stack und Positioned
Stack und Positioned in Flutter: wie ein Stack seine Größe bestimmt, fit, alignment, clipBehavior, Positioned.fill, PositionedDirectional, Badge und Overlays.
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.