Het probleem

Een Flutter-app die naar telefoons, tablets en desktop gaat, heeft meer nodig dan een flexibele kolom. Een scherm dat prettig leest bij 390 logische pixels breed wil bij 700 een gecentreerde kaart en bij 1200 een zijbalk. Flutter geeft je de metingen, maar geen structuur om ernaar te handelen.

Dus verspreiden de controles zich. Hier een MediaQuery.of(context).size.width > 600, daar een LayoutBuilder, en drie widgets verderop een ternaire operator binnen een Padding. Elk daarvan is op zichzelf te verdedigen; samen vormen ze een lay-outbeleid dat nergens op één plek staat opgeschreven.

Veelvoorkomende problemen:

  • Breekpuntwaarden die in tientallen widgets zijn gedupliceerd en uit elkaar lopen naarmate het ontwerp verandert
  • LayoutBuilder die de beperkingen van de ouder meldt terwijl je het toestel wilde weten
  • Lay-outs die naar de tabletvariant omslaan zodra een telefoon liggend wordt gedraaid
  • Widgetbomen waarin de responsieve vertakking onzichtbaar is zonder elk kind te lezen

Hoe flutter_adaptive_layout dat oplost

AdaptiveLayout neemt één builder per schermgrootte en een child. Het meet het toestel via MediaQuery, vertaalt dat naar een ScreenSize, en bouwt alleen de passende variant — waarbij de child één keer wordt gemaakt en aan de winnende builder wordt overhandigd.

AdaptiveLayout(
  smallBuilder: (context, child) => child!,
  mediumBuilder: (context, child) => Center(child: child),
  largeBuilder: (context, child) => Row(
    children: [const Sidebar(), Expanded(child: child!)],
  ),
  child: const MyHomePage(),
)

Het responsieve besluit leeft nu in één widget, bovenaan de subboom die het bestuurt, geschreven als drie lay-outs in plaats van een keten voorwaarden.

Wat het ondersteunt

  • Lay-outs gestuurd door schermgroottesmallBuilder, mediumBuilder en largeBuilder, elk optioneel; een ontbrekende valt terug op child, zodat je alleen de groottes hoeft vorm te geven waar het je om gaat
  • Breekpunten die jij bepaalt — standaard 400 en 600 logische pixels, per widget of voor de hele app te overschrijven
  • Bouw de child één keer — van lay-out wisselen verpakt je inhoud in plaats van haar opnieuw op te bouwen
  • Stabiel bij draaien — de indeling leest MediaQuery.of(context).size.shortestSide, zodat een telefoon liggend klein blijft
  • Inwisselbare indelingslogica — implementeer ScreenSizeQualifier om in te delen op breedte, platform, scharnierstand of wat je designsysteem ook gebruikt
  • Geen afhankelijkheden — puur Flutter boven op MediaQuery, werkend op iOS, Android, web, macOS, Windows en Linux

Hoe de schermgrootte wordt bepaald

De standaard BreakpointsQualifier vergelijkt de kortste zijde met twee breekpunten, inclusief — een waarde die precies gelijk is aan een breekpunt hoort bij de kleinere categorie.

SchermgrootteVoorwaardeStandaardbereikTypisch toestel
ScreenSize.smallshortestSide <= smallBreakpoint0–400 logische pixelsTelefoons
ScreenSize.mediumshortestSide <= mediumBreakpoint401–600 logische pixelsKleine tablets
ScreenSize.largeboven mediumBreakpoint601+ logische pixelsGrote tablets, desktop

Breekpunten worden in een vaste volgorde opgelost, waarbij de eerst gevonden waarde wint: de argumenten die aan BreakpointsQualifier zijn meegegeven, dan de dichtstbijzijnde BreakpointsSetting erboven, dan de ingebouwde 400 en 600.

Toepassingen in de praktijk

Telefoon-eerst-apps die een tabletversie krijgen

Een app die op telefoons begon kan elk scherm houden dat er al is en een largeBuilder toevoegen waar een bredere lay-out loont. Schermen zonder builder voor een bepaalde grootte renderen de child ongewijzigd, zodat de migratie scherm voor scherm gaat in plaats van in één keer.

Een lijst die op een telefoon een detailroute opduwt, kan op een tablet lijst en detail naast elkaar tonen. De twee varianten staan naast elkaar gedeclareerd in één widget, waardoor het verschil ertussen in een diff te beoordelen is.

Designsystemen met eigen breekpunten

Wikkel MaterialApp één keer in BreakpointsSetting en elke AdaptiveLayout in de boom neemt de getallen van het designsysteem over. Ze later wijzigen is een aanpassing van één regel, geen zoektocht door de codebase.

Vensters op web en desktop

Op web en desktop is de gemeten grootte het venster, dus lay-outs reageren terwijl de gebruiker het formaat verandert — hetzelfde codepad dat een tablet afhandelt, handelt een verkleinde browser af.

Aan de slag

Installeer het pakket:

flutter pub add flutter_adaptive_layout

Vereist Dart >=2.18.5 <4.0.0 en Flutter 1.17+.

Importeer het daarna en wikkel de widget waarvan de lay-out zich moet aanpassen:

import 'package:flutter_adaptive_layout/flutter_adaptive_layout.dart';

AdaptiveLayout(
  largeBuilder: (context, child) => Center(
    child: SizedBox(width: 600, child: child),
  ),
  child: const ArticleView(), // used as-is on small and medium screens
)

Wil je schermen anders indelen, implementeer dan ScreenSizeQualifier en geef die mee als qualifier:

class WidthQualifier extends ScreenSizeQualifier {
  @override
  ScreenSize qualify(BuildContext context) {
    final width = MediaQuery.of(context).size.width;
    if (width < 600) return ScreenSize.small;
    if (width < 1024) return ScreenSize.medium;
    return ScreenSize.large;
  }
}

qualify draait bij elke build, dus houd hem goedkoop en vrij van neveneffecten.

Een complete, draaibare app staat in de voorbeeldmap.

LayoutBuilder meldt de beperkingen die de ouderwidget doorgeeft, en die kunnen veel kleiner zijn dan het scherm. AdaptiveLayout deelt het toestel of venster in via MediaQuery, zodat een widget diep in de boom nog steeds weet dat hij op een tablet draait. Gebruik LayoutBuilder om in een beschikbaar kader te passen; gebruik AdaptiveLayout om een lay-out voor het toestel te kiezen.
Nee. De indeling gebruikt MediaQuery.of(context).size.shortestSide, dat staand en liggend hetzelfde is, dus een telefoon blijft ScreenSize.small bij draaien. Wil je lay-outs die op oriëntatie reageren, implementeer dan een eigen ScreenSizeQualifier die size.width leest.
Nee. Elke builder mag worden weggelaten en dan wordt voor die schermgrootte de child gebruikt. Geen passende builder én geen child opgeven werpt een UnimplementedError.
Geef BreakpointsQualifier(smallBreakpoint: ..., mediumBreakpoint: ...) mee aan één AdaptiveLayout, of wikkel je app in BreakpointsSetting om ze overal te wijzigen. Waarden uit de constructor winnen van BreakpointsSetting, dat weer wint van de standaardwaarden 400 en 600.
Ja. Het hangt alleen van MediaQuery af, dus het draait op elk Flutter-doelplatform. Op web en desktop is de gemeten grootte het venster, dus lay-outs reageren terwijl het venster van formaat verandert.