Skip to content

Instantly share code, notes, and snippets.

@100xA
Created January 14, 2026 08:51
Show Gist options
  • Select an option

  • Save 100xA/e164a55763d7a018cdfa7fcc5b52e6d0 to your computer and use it in GitHub Desktop.

Select an option

Save 100xA/e164a55763d7a018cdfa7fcc5b52e6d0 to your computer and use it in GitHub Desktop.
# Flutter Responsive Layouts Workshop

1. Einführung

Responsive vs. Adaptive Design

  • 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

Der offizielle Flutter-Ansatz: Abstract → Measure → Branch

  1. Abstract: Widgets identifizieren, die dynamisches Verhalten brauchen
  2. Measure: Verfügbaren Platz messen (MediaQuery oder LayoutBuilder)
  3. Branch: Breakpoints anwenden und passendes UI rendern

2. Core Widgets

2.1 MediaQuery – Bildschirm-Level Anpassungen

Wichtig: Immer spezifische Accessors verwenden!

  • 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;

Verfügbare spezifische Accessors

// 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

Wann MediaQuery verwenden?

  • Globale Bildschirm-Breakpoints (Navigation wechseln)
  • Accessibility-Settings abfragen
  • Safe Area Insets
  • NICHT für wiederverwendbare Komponenten (→ LayoutBuilder)

Quelle: MediaQuery class - Flutter API


2.2 LayoutBuilder – Widget-Level Anpassungen

  • Liefert BoxConstraints vom 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();
  },
)

MediaQuery vs. LayoutBuilder – Entscheidungshilfe

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


2.3 Flexible Layout Widgets

Expanded vs. Flexible

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:

FractionallySizedBox

  • 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

Wrap – Automatischer Zeilenumbruch

  • 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

FittedBox – Skalierung

// 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


3. Material 3 Breakpoints

Standard Window Size Classes

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

Der wichtigste Breakpoint: 600px

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,
      ),
    );
  }
}

4. Packages Übersicht

flutter_screenutil (v5.9.3) – Design-Spec Implementation

  • 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

auto_size_text (v3.0.0) – Flutter Favorite

  • 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!

responsive_framework (v1.5.1) – Web-Style Breakpoints

  • 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

Package-Empfehlung für Mobile

Anforderung Package
Pixel-perfekte Designs flutter_screenutil
Automatische Text-Skalierung auto_size_text
Komplexe Breakpoint-Logik responsive_framework
Einfache Anpassungen Built-in Widgets (bevorzugt!)

6. FYI Update

Row/Column spacing Parameter (Flutter 3.27+)

  • 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(),
  ],
)

Ressourcen

Offizielle Dokumentation

Beispiel-App

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment