İçeriğe geç / Skip to content / Zum Inhalt

Flutter: Feine Ausrichtung mit dem Align-Widget

Ahmet Balaman

Zuletzt aktualisiert:

7 Min. Lesezeit

FlutterAlignAlignmentStackLayoutAusrichtung
Flutter: Feine Ausrichtung mit dem Align-Widget

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:

Das Alignment-Koordinatensystem: oben links (-1, -1), Mitte (0, 0), unten rechts (1, 1)

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:

  1. Begrenzt der Parent die Achse, nimmt Align die größtmögliche Größe an.
  2. Ist der Parent auf dieser Achse unbegrenzt (die horizontale Achse einer Row, die vertikale Achse einer Column), schrumpft Align auf sein Kind.
  3. Ist widthFactor oder heightFactor gesetzt, 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) oder Transform.translate (überall).
  • Wenn ohnehin ein Container da ist, Container(alignment: ...); er baut intern dasselbe Align.
  • Um alle Kinder auf einmal auszurichten, die Parameter mainAxisAlignment und crossAxisAlignment von 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.

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.

Kommentare