Baue Oberflächen, die sich an die Constraints des Elternteils, an das Fenster und an die Konventionen jedes Betriebssystems anpassen.
Diese Lektion in Kodokon öffnenMediaQuery beschreibt das logische Fenster, nicht den physischen Bildschirm: Größe, padding (Notch, Systemleiste), viewInsets (Tastatur), textScaler... Eine klassische Falle: MediaQuery.of(context) meldet das Widget bei allen diesen Eigenschaften an. Wenn sich die Tastatur öffnet, ändert sich viewInsets und jedes angemeldete Widget wird neu gebaut - auch die, die nur die Breite lesen. Seit Flutter 3.10 wird MediaQueryData über ein InheritedModel bereitgestellt: Die gezielten Zugriffsmethoden MediaQuery.sizeOf(context), paddingOf, viewInsetsOf... melden das Widget nur bei dem angefragten Aspekt an. Reflex der Profis: Verbanne .of(context).size aus deiner Codebasis.
import 'package:flutter/material.dart';
class AdaptiveGrid extends StatelessWidget {
const AdaptiveGrid({super.key});
@override
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
final columns = switch (width) {
>= 900 => 4,
>= 600 => 3,
_ => 2,
};
return GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
),
itemCount: 24,
itemBuilder: (context, i) => Card(
child: Center(child: Text('Tile $i')),
),
);
}
}MediaQuery beantwortet die Frage "Wie groß ist das Fenster?"; LayoutBuilder beantwortet "Wie viel Platz gibt mir mein Elternteil?". Dieser Unterschied ist für wiederverwendbare Komponenten entscheidend: Ein Panel, das mal bildschirmfüllend, mal in einer 320 px breiten Spalte angezeigt wird, muss auf seine Constraints reagieren, nicht auf das Fenster - sonst hält es sich für ein Tablet, während es in Wahrheit eingezwängt ist. Ein nützliches inneres Detail: Der builder von LayoutBuilder läuft während der Layout-Phase, wenn die Constraints bekannt sind; du kannst darin daher kein synchrones setState auslösen, das Framework verbietet es.
import 'package:flutter/material.dart';
class MasterDetail extends StatelessWidget {
const MasterDetail({
super.key,
required this.list,
required this.detail,
});
final Widget list;
final Widget detail;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 720) {
return list;
}
return Row(
children: [
SizedBox(width: 320, child: list),
const VerticalDivider(width: 1),
Expanded(child: detail),
],
);
},
);
}
}Für die Unterschiede zwischen iOS und Android ist auf Widget-Seite Theme.of(context).platform die Quelle der Wahrheit: Sie leitet sich von defaultTargetPlatform ab und respektiert dabei die Overrides - unverzichtbar, um die iOS-Darstellung aus einem Test heraus zu prüfen oder eine andere Plattform zu simulieren. Viele Anpassungen sind bereits im Framework eingebaut: die Scroll-Physik (das Nachfedern unter iOS gegenüber dem Leuchten unter Android), die Seitenübergänge, die Wischgeste vom Rand zum Zurückgehen. Die .adaptive-Konstruktoren (Switch.adaptive, CircularProgressIndicator.adaptive) wechseln von selbst zur Cupertino-Darstellung. Hebe dir eigene Verzweigungen für wirklich abweichende Konventionen auf, etwa die Beschriftung von Aktionen oder die Position der Buttons in Dialogen.
import 'package:flutter/material.dart';
class SettingsTile extends StatelessWidget {
const SettingsTile({
super.key,
required this.value,
required this.onChanged,
});
final bool value;
final ValueChanged<bool> onChanged;
@override
Widget build(BuildContext context) {
final platform = Theme.of(context).platform;
final isApple = platform == TargetPlatform.iOS ||
platform == TargetPlatform.macOS;
return ListTile(
title: Text(
isApple ? 'Settings' : 'Preferences',
),
trailing: Switch.adaptive(
value: value,
onChanged: onChanged,
),
);
}
}