- Responsive Design: UI passt sich an verfügbaren Platz an (gleiche Komponenten, andere Größe)
- Adaptive Design: UI verhält sich anders je nach Kontext (andere Komponenten/Navigation)
- Flutter kombiniert beides – wir brauchen beide Ansätze
Quelle: Flutter - General approach to adaptive apps
- Abstract: Widgets identifizieren, die dynamisches Verhalten brauchen
- Measure: Verfügbaren Platz messen (
MediaQueryoderLayoutBuilder) - Branch: Breakpoints anwenden und passendes UI rendern
MediaQuery.of(context)verursacht Rebuilds bei JEDER Änderung (auch Keyboard!)- Spezifische Accessors rebuilden nur bei relevanten Änderungen
// SCHLECHT - Rebuild bei jeder MediaQuery-Änderung
final data = MediaQuery.of(context);
final width = data.size.width;
// GUT - Rebuild nur bei Size-Änderung
final size = MediaQuery.sizeOf(context);
final width = size.width;// Größe & Orientierung
MediaQuery.sizeOf(context) // Size (width, height)
MediaQuery.orientationOf(context) // Portrait/Landscape
// Safe Areas (Notch, Dynamic Island, etc.)
MediaQuery.paddingOf(context) // Dynamisch (berücksichtigt Keyboard)
MediaQuery.viewPaddingOf(context) // Statisch (ignoriert Keyboard)
MediaQuery.viewInsetsOf(context) // Keyboard-Höhe
// Accessibility
MediaQuery.textScalerOf(context) // Text-Skalierung
MediaQuery.platformBrightnessOf(context) // Dark/Light Mode- Globale Bildschirm-Breakpoints (Navigation wechseln)
- Accessibility-Settings abfragen
- Safe Area Insets
- NICHT für wiederverwendbare Komponenten (→ LayoutBuilder)
Quelle: MediaQuery class - Flutter API
- Liefert
BoxConstraintsvom Parent-Widget (nicht vom Bildschirm!) - Perfekt für wiederverwendbare Komponenten
- Widget passt sich an seinen Container an, nicht an den Screen
LayoutBuilder(
builder: (context, constraints) {
// constraints.maxWidth = verfügbare Breite vom Parent
if (constraints.maxWidth >= 600) {
return WideCardLayout();
}
return NarrowCardLayout();
},
)| Kriterium | MediaQuery | LayoutBuilder |
|---|---|---|
| Misst | Bildschirm/Window | Parent-Container |
| Nutzen für | Screen-Level Layouts | Reusable Components |
| Beispiel | Navigation wechseln | Card-Layout anpassen |
// Beispiel: Reusable ProductCard
class ProductCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final isWide = constraints.maxWidth > 300;
return isWide
? Row(children: [_Image(), Expanded(child: _Details())])
: Column(children: [_Image(), _Details()]);
},
);
}
}Quelle: LayoutBuilder class - Flutter API
Row(
children: [
// Expanded: MUSS allen verfügbaren Platz füllen (FlexFit.tight)
Expanded(
flex: 2,
child: Container(color: Colors.red),
),
// Flexible: KANN bis zu verfügbarem Platz wachsen (FlexFit.loose)
Flexible(
flex: 1,
child: Container(color: Colors.blue),
),
],
)- Expanded: Kind füllt IMMER allen Platz (tight)
- Flexible: Kind kann kleiner sein als verfügbar (loose)
flex-Wert bestimmt Verhältnis bei mehreren Expanded/Flexible
Quelle:
- Größe als Bruchteil des Parent-Containers
// 80% der Parent-Breite, 50% der Parent-Höhe
FractionallySizedBox(
widthFactor: 0.8,
heightFactor: 0.5,
child: Container(color: Colors.green),
)
// In Row/Column: MUSS in Flexible gewrappt werden!
Row(
children: [
Flexible(
child: FractionallySizedBox(
widthFactor: 0.5,
child: MyWidget(),
),
),
],
)Quelle: FractionallySizedBox class - Flutter API
- Kinder werden automatisch umgebrochen wenn kein Platz mehr
Wrap(
spacing: 8.0, // Horizontaler Abstand
runSpacing: 8.0, // Vertikaler Abstand zwischen Zeilen
children: [
Chip(label: Text('Flutter')),
Chip(label: Text('Dart')),
Chip(label: Text('Responsive')),
Chip(label: Text('Design')),
],
)- Perfekt für: Tags, Chips, Filter-Buttons
Quelle: Wrap class - Flutter API
// Skaliert Kind um in Container zu passen
FittedBox(
fit: BoxFit.scaleDown, // Nur verkleinern, nie vergrößern
child: Text(
'Dieser Text wird verkleinert wenn nötig',
style: TextStyle(fontSize: 24),
),
)BoxFit.scaleDown: Verkleinert nur wenn nötig (häufigster Use Case)BoxFit.contain: Passt an ohne zu beschneiden
Quelle: FittedBox class - Flutter API
| Klasse | Breite | Navigation Pattern |
|---|---|---|
| Compact | 0–599px | Bottom NavigationBar |
| Medium | 600–839px | NavigationRail |
| Expanded | 840–1199px | NavigationRail + Labels |
| Large | 1200px+ | NavigationDrawer |
Material Design 3 - Window size classes
Android Developers - Use window size classes
Material Design (Legacy) - Responsive UI Layout
- < 600px: Mobile → Bottom Navigation
- ≥ 600px: Tablet → NavigationRail Material Design - Responsive UI breakpoints
class ResponsiveScaffold extends StatelessWidget {
final Widget body;
final int selectedIndex;
final ValueChanged<int> onDestinationSelected;
const ResponsiveScaffold({
required this.body,
required this.selectedIndex,
required this.onDestinationSelected,
});
@override
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
final isWideScreen = width >= 600;
final destinations = [
NavigationDestination(icon: Icon(Icons.home), label: 'Home'),
NavigationDestination(icon: Icon(Icons.search), label: 'Search'),
NavigationDestination(icon: Icon(Icons.person), label: 'Profile'),
];
if (isWideScreen) {
return Scaffold(
body: Row(
children: [
NavigationRail(
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
labelType: NavigationRailLabelType.all,
destinations: destinations.map((d) =>
NavigationRailDestination(
icon: d.icon,
label: Text(d.label),
),
).toList(),
),
const VerticalDivider(thickness: 1, width: 1),
Expanded(child: body),
],
),
);
}
return Scaffold(
body: body,
bottomNavigationBar: NavigationBar(
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
destinations: destinations,
),
);
}
}- 1.37M Downloads – Der Standard für pixel-perfekte Designs
- Skaliert Dimensionen relativ zu einer Design-Referenzgröße
// Setup in main.dart
ScreenUtilInit(
designSize: const Size(375, 812), // iPhone X als Referenz
minTextAdapt: true,
builder: (context, child) => MaterialApp(
home: HomeScreen(),
),
);
// Verwendung mit Extensions
Container(
width: 100.w, // Skaliert mit Bildschirmbreite
height: 50.h, // Skaliert mit Bildschirmhöhe
padding: EdgeInsets.all(16.r), // Skaliert mit kleinerem Faktor
child: Text(
'Responsive Text',
style: TextStyle(fontSize: 16.sp), // Skaliert mit Textscaler
),
)Wann nutzen:
- Designer liefern Pixel-Specs (Figma/Sketch)
- Konsistente Proportionen auf allen Geräten
- Nicht geeignet für echte adaptive Layouts+
Quelle: flutter_screenutil
- Text skaliert automatisch um in Container zu passen
AutoSizeText(
'Dieser Text passt sich automatisch an!',
maxLines: 2,
minFontSize: 12, // Minimale Schriftgröße
maxFontSize: 24, // Maximale Schriftgröße
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 24),
)Wichtig: Parent muss begrenzte Constraints haben!
- Bootstrap-ähnliche Breakpoint-Definitionen
MaterialApp(
builder: (context, child) => ResponsiveBreakpoints.builder(
child: child!,
breakpoints: [
const Breakpoint(start: 0, end: 450, name: MOBILE),
const Breakpoint(start: 451, end: 800, name: TABLET),
const Breakpoint(start: 801, end: 1920, name: DESKTOP),
],
),
home: HomeScreen(),
);
// Abfragen
if (ResponsiveBreakpoints.of(context).isMobile) {
return MobileLayout();
}Quelle: responsive_framework
| Anforderung | Package |
|---|---|
| Pixel-perfekte Designs | flutter_screenutil |
| Automatische Text-Skalierung | auto_size_text |
| Komplexe Breakpoint-Logik | responsive_framework |
| Einfache Anpassungen | Built-in Widgets (bevorzugt!) |
- Keine SizedBox-Spacer mehr nötig!
// Vorher (Flutter < 3.27)
Row(
children: [
Widget1(),
SizedBox(width: 16),
Widget2(),
SizedBox(width: 16),
Widget3(),
],
)
// Jetzt (Flutter 3.27+)
Row(
spacing: 16.0, // Automatischer Abstand zwischen Kindern
children: [
Widget1(),
Widget2(),
Widget3(),
],
)