Appearance
Kapitel 6: Flutter Layouts
6.1 Grundlegende Layout-Prinzipien
Alles ist ein Widget
In Flutter werden Layouts durch Widgets erreicht. Es gibt keine separaten Layout-Sprachen (wie HTML/CSS).
Layout-Achsen
Hauptachse (Main Axis):
MainAxisAlignment: Ausrichtung entlang der HauptachseMainAxisSize: Größe der Hauptachse
Querachse (Cross Axis):
CrossAxisAlignment: Ausrichtung entlang der Querachse
Row (Horizontal):
Hauptachse: Horizontal (von links nach rechts)
Querachse: Vertikal (von oben nach unten)
Column (Vertikal):
Hauptachse: Vertikal (von oben nach unten)
Querachse: Horizontal (von links nach rechts)6.2 Lineare Layouts (Row & Column)
Row (Horizontales Layout)
dart
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, // Hauptachse
crossAxisAlignment: CrossAxisAlignment.center, // Querachse
children: <Widget>[
Icon(Icons.home),
Text('Startseite'),
ElevatedButton(onPressed: () {}, child: Text('Go')),
],
)MainAxisAlignment-Optionen:
MainAxisAlignment.start: Am Anfang (links/oben)MainAxisAlignment.end: Am Ende (rechts/unten)MainAxisAlignment.center: ZentriertMainAxisAlignment.spaceBetween: Gleichmäßiger Abstand (ohne Rand)MainAxisAlignment.spaceAround: Gleichmäßiger Abstand (mit halbem Rand)MainAxisAlignment.spaceEvenly: Vollständig gleicher Abstand
CrossAxisAlignment-Optionen:
CrossAxisAlignment.start: Am Anfang der QuerachseCrossAxisAlignment.end: Am Ende der QuerachseCrossAxisAlignment.center: Zentriert (Standard)CrossAxisAlignment.stretch: Dehnt Kinder auf volle Querachsen-Breite
Column (Vertikales Layout)
dart
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('Zeile 1'),
Text('Zeile 2'),
ElevatedButton(onPressed: () {}, child: Text('Button')),
],
)Expanded (Platz füllen)
dart
Row(
children: <Widget>[
Expanded(
flex: 2, // 2/3 des verfügbaren Platzes
child: Container(color: Colors.red, height: 50),
),
Expanded(
flex: 1, // 1/3 des verfügbaren Platzes
child: Container(color: Colors.blue, height: 50),
),
],
)Spacer (Abstand füllen)
dart
Row(
children: <Widget>[
Text('Links'),
const Spacer(), // Drückt nachfolgende Widgets nach rechts
Text('Rechts'),
],
)6.3 Flex-Layout (Flex & Expanded)
Flex-Widget
Flex ist die Basis von Row und Column.
dart
Flex(
direction: Axis.horizontal, // Oder Axis.vertical
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
// ...
],
)Flexible (Flexible Größe)
dart
Row(
children: <Widget>[
Flexible(
flex: 1,
fit: FlexFit.loose, // Kann schrumpfen (Standard)
child: Container(color: Colors.red, height: 50),
),
Flexible(
flex: 2,
fit: FlexFit.tight, // Füllt verfügbaren Platz (wie Expanded)
child: Container(color: Colors.blue, height: 50),
),
],
)Unterschied: Expanded vs. Flexible
Expanded=Flexiblemitfit: FlexFit.tightExpandedfüllt immer verfügbaren PlatzFlexiblekann (standardmäßig) schrumpfen
6.4 Fluss-Layout (Wrap & Flow)
Wrap (Fluss-Layout)
Wrap erlaubt Zeilenumbruch, wenn der Platz nicht ausreicht.
dart
Wrap(
spacing: 8.0, // Horizontaler Abstand
runSpacing: 4.0, // Vertikaler Abstand
alignment: WrapAlignment.center,
children: <Widget>[
Chip(label: Text('Flutter')),
Chip(label: Text('Dart')),
Chip(label: Text('Android')),
Chip(label: Text('iOS')),
Chip(label: Text('Web')),
],
)Anwendungsfälle:
- Tags/Chips anzeigen
- Buttons in einer flexiblen Zeile
- Überlauf vermeiden
Flow (Benutzerdefiniertes Fluss-Layout)
Fortgeschritten – meist nicht notwendig für Anfänger.
6.5 Stapel-Layout (Stack & Positioned)
Stack (Stapel-Container)
Stack erlaubt Widgets übereinander zu stapeln.
dart
Stack(
alignment: Alignment.center, // Standardausrichtung
children: <Widget>[
Container(width: 200, height: 200, color: Colors.red),
Container(width: 150, height: 150, color: Colors.blue),
Container(width: 100, height: 100, color: Colors.green),
],
)Positioned (Positionierung in Stack)
dart
Stack(
children: <Widget>[
Container(width: 300, height: 300, color: Colors.grey.shade200),
// Oben links
const Positioned(
top: 10,
left: 10,
child: Icon(Icons.home, size: 30),
),
// Unten rechts
const Positioned(
bottom: 10,
right: 10,
child: Icon(Icons.settings, size: 30),
),
// Zentriert
const Center(
child: Text('Zentriert', style: TextStyle(fontSize: 20)),
),
],
)Anwendungsfälle:
- Bilder mit Overlay-Text
- Badge auf Icon
- Floating Action Button positionieren
6.6 Card-Layout (Karte)
dart
Card(
elevation: 4, // Schattenstärke
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text('Kartentitel', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
const Text('Dies ist der Inhalt der Karte.'),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
TextButton(onPressed: () {}, child: const Text('Abbrechen')),
ElevatedButton(onPressed: () {}, child: const Text('OK')),
],
),
],
),
),
)6.7 Layout-Anpassung (Responsive Design)
MediaQuery (Bildschirmgröße abrufen)
dart
class ResponsivePage extends StatelessWidget {
const ResponsivePage({super.key});
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final width = size.width;
return Scaffold(
body: width > 600 // Tablet/Desktop
? _buildWideLayout()
: _buildNarrowLayout(), // Smartphone
);
}
Widget _buildNarrowLayout() {
return const Center(child: Text('Schmal (Smartphone)'));
}
Widget _buildWideLayout() {
return const Center(child: Text('Breit (Tablet/Desktop)'));
}
}flutter_screenutil (Plugin empfohlen)
Installation (pubspec.yaml):
yaml
dependencies:
flutter_screenutil: ^5.9.0Verwendung:
dart
import 'package:flutter_screenutil/flutter_screenutil.dart';
// In MaterialApp initialisieren
MaterialApp(
home: Scaffold(
body: Builder(
builder: (context) {
// Initialize ScreenUtil
ScreenUtil.init(context, designSize: const Size(360, 690));
return Container(
width: 300.w, // .w = breite relativ zur Designgröße
height: 200.h, // .h = höhe relativ zur Designgröße
padding: EdgeInsets.all(20.r), // .r = radius/size relativ
child: Text(
'Adaptiver Text',
style: TextStyle(fontSize: 24.sp), // .sp = schriftgröße relativ
),
);
},
),
),
)6.8 Praxisbeispiel: Startseiten-Layout erstellen
dart
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Startseite')),
body: SingleChildScrollView( // Scrollbar bei Überlauf
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
// Banner-Bereich
Container(
height: 200,
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue, Colors.purple],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: const Center(
child: Text(
'Willkommen!',
style: TextStyle(fontSize: 32, color: Colors.white, fontWeight: FontWeight.bold),
),
),
),
const SizedBox(height: 20),
// Kategorien (horizontal scrollbar)
SizedBox(
height: 100,
child: ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
_buildCategoryCard(Icons.code, 'Flutter'),
_buildCategoryCard(Icons.android, 'Android'),
_buildCategoryCard(Icons.phone_iphone, 'iOS'),
_buildCategoryCard(Icons.web, 'Web'),
_buildCategoryCard(Icons.desktop_windows, 'Desktop'),
],
),
),
const SizedBox(height: 20),
// Neuigkeiten-Bereich (Grid)
Padding(
padding: const EdgeInsets.all(16.0),
child: GridView.count(
shrinkWrap: true, // WICHTIG: In Column verwendbar
physics: const NeverScrollableScrollPhysics(), // Scroll deaktivieren
crossAxisCount: 2, // 2 Spalten
crossAxisSpacing: 10,
mainAxisSpacing: 10,
childAspectRatio: 0.75, // Breite/Höhe Verhältnis
children: <Widget>[
_buildNewsCard('Neuigkeit 1', Icons.article),
_buildNewsCard('Neuigkeit 2', Icons.article),
_buildNewsCard('Neuigkeit 3', Icons.article),
_buildNewsCard('Neuigkeit 4', Icons.article),
],
),
),
],
),
),
);
}
// Hilfsfunktion: Kategorie-Karte
Widget _buildCategoryCard(IconData icon, String label) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircleAvatar(
radius: 30,
backgroundColor: Colors.blue.shade100,
child: Icon(icon, size: 30, color: Colors.blue),
),
const SizedBox(height: 8),
Text(label),
],
),
);
}
// Hilfsfunktion: Neuigkeiten-Karte
Widget _buildNewsCard(String title, IconData icon) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(icon, size: 50, color: Colors.blue),
const SizedBox(height: 16),
Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
const Text('Kurze Beschreibung der Neuigkeit...', textAlign: TextAlign.center),
],
),
),
);
}
}6.9 Häufige Fehler
Fehler 1: Layout-Überlauf (Renderflex overflowed)
Fehler:
════════ Exception caught by rendering library ═══════
A RenderFlex overflowed by 100 pixels on the right.Ursache: Widget passt nicht in verfügbaren Platz.
Lösungen:
Expandedverwenden
dart
Row(
children: <Widget>[
Expanded(child: Text('Sehr langer Text der überläuft...')),
Icon(Icons.more_vert),
],
)SingleChildScrollViewverwenden
dart
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(...),
)Flexibleverwenden
dart
Row(
children: <Widget>[
Flexible(child: Text('Langer Text...')),
Icon(Icons.more_vert),
],
)Fehler 2: Ausrichtung falsch
Fehler: Widgets sind nicht dort, wo sie sein sollten.
Prüfung:
dart
// Debugging: Rote Linien für Layout-Grenzen anzeigen
import 'package:flutter/rendering.dart';
void main() {
debugPaintSizeEnabled = true; // Aktiviert Visual Debugging
runApp(MyApp());
}Fehler 3: Zu tiefe Verschachtelung
Schlecht (zu tief verschachtelt):
dart
Column(
children: <Widget>[
Row(
children: <Widget>[
Column(
children: <Widget>[
Row(
children: <Widget>[
Text('Zu tief!'),
],
),
],
),
],
),
],
)Besser: Widgets extrahieren
dart
class MyComplexWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(...); // Einfacher zu verstehen
}
}Zusammenfassung
In diesem Kapitel haben Sie:
- ✅ Grundlegende Layout-Prinzipien verstanden
- ✅ Lineare Layouts (
Row,Column) gemeistert - ✅
Expanded,Flexible,Spacerverwendet - ✅ Fluss-Layout (
Wrap) zur Überlaufvermeidung gelernt - ✅ Stapel-Layout (
Stack,Positioned) für Overlays verwendet - ✅
Card-Layout für schöne Karten gelernt - ✅ Responsive Design mit
MediaQuery&flutter_screenutilimplementiert - ✅ Ein komplexes Startseiten-Layout erstellt
- ✅ Häufige Layout-Fehler behoben
Nächstes Kapitel: Wir werden Zustandsverwaltung lernen (StatefulWidget, setState, Provider).
Übungsaufgaben:
- Erstellen Sie eine Produktliste mit
ListViewundCard - Erstellen Sie ein Responsives Layout, das auf Smartphone und Tablet unterschiedlich aussieht
- Verwenden Sie
Stack, um ein Bild mit Text-Overlay zu erstellen - Erstellen Sie eine Tag-Liste mit
Wrap, die bei Überlauf umbricht - Beheben Sie einen Layout-Überlauf-Fehler in einer bestehenden App
Häufige Fehler:
- ❌
Expandedaußerhalb vonRow/Columnverwenden →Expandedmuss inRow/Columnsein - ❌
ListViewinColumnohneshrinkWrap: trueverwenden → Überlauf! - ❌
StackohnePositionedverwenden und sich über Überlappung wundern - ❌ Zu tiefe Verschachtelung → Performance-Probleme & schlechte Lesbarkeit
