Creare layout responsive in Flutter con LayoutBuilder e MediaQuery
GuideIntermedio35 min Flutter 3.x

Creare layout responsive in Flutter con LayoutBuilder e MediaQuery

Le app Flutter possono girare su dispositivi con dimensioni dello schermo molto diverse: telefoni, tablet, web e desktop. Per offrire una buona esperienza utente è fondamentale costruire layout responsive che si adattino allo spazio disponibile.

In questo tutorial vedremo come usare MediaQuery per leggere le dimensioni dello schermo, LayoutBuilder per reagire ai vincoli del contenitore e widget come Flexible, Expanded e Wrap per costruire interfacce adattive. Realizzeremo una schermata che mostra una griglia di card che cambia layout in base alla larghezza disponibile.

  1. 1

    Comprendere MediaQuery e LayoutBuilder

    Prima di scrivere codice è importante capire la differenza tra i due strumenti principali:

    • MediaQuery: fornisce informazioni sull'intero schermo (dimensioni, orientamento, padding di sistema, scala del testo). È utile per decisioni globali.
    • LayoutBuilder: fornisce i vincoli (constraints) dello spazio in cui un widget viene effettivamente posizionato. È perfetto quando un widget potrebbe non occupare tutto lo schermo (es. dentro una colonna o un pannello laterale).

    In generale: usa MediaQuery per scelte di alto livello e LayoutBuilder per adattamenti locali.

    // Lettura delle dimensioni dello schermo
    final size = MediaQuery.of(context).size;
    final larghezza = size.width;
    final orientamento = MediaQuery.of(context).orientation;
    
    print('Larghezza schermo: $larghezza');
    print('Orientamento: $orientamento');

    Risultato atteso

    Hai chiaro quando usare MediaQuery e quando LayoutBuilder.

  2. 2

    Definire i breakpoint dell'app

    Per gestire diversi tipi di dispositivi conviene definire dei breakpoint centralizzati. Creiamo una classe di utilità con soglie standard per mobile, tablet e desktop, così da evitare di ripetere numeri magici nel codice.

    class Breakpoints {
      static const double mobile = 600;
      static const double tablet = 1024;
    }
    
    enum TipoDispositivo { mobile, tablet, desktop }
    
    TipoDispositivo tipoDispositivo(double larghezza) {
      if (larghezza < Breakpoints.mobile) return TipoDispositivo.mobile;
      if (larghezza < Breakpoints.tablet) return TipoDispositivo.tablet;
      return TipoDispositivo.desktop;
    }

    Risultato atteso

    Hai una funzione riutilizzabile che classifica il dispositivo in base alla larghezza.

  3. 3

    Creare un widget responsive con LayoutBuilder

    Costruiamo un widget LayoutResponsivo che mostra contenuti diversi in base ai vincoli ricevuti. Usiamo LayoutBuilder perché reagisce allo spazio reale assegnato al widget, non all'intero schermo.

    import 'package:flutter/material.dart';
    
    class LayoutResponsivo extends StatelessWidget {
      final Widget mobile;
      final Widget tablet;
      final Widget desktop;
    
      const LayoutResponsivo({
        super.key,
        required this.mobile,
        required this.tablet,
        required this.desktop,
      });
    
      @override
      Widget build(BuildContext context) {
        return LayoutBuilder(
          builder: (context, constraints) {
            final tipo = tipoDispositivo(constraints.maxWidth);
            switch (tipo) {
              case TipoDispositivo.mobile:
                return mobile;
              case TipoDispositivo.tablet:
                return tablet;
              case TipoDispositivo.desktop:
                return desktop;
            }
          },
        );
      }
    }

    Risultato atteso

    Hai un widget riutilizzabile che mostra layout differenti per ogni dispositivo.

  4. 4

    Costruire una griglia adattiva con GridView

    Un caso comune è mostrare delle card in una griglia il cui numero di colonne cambia in base alla larghezza. Usiamo GridView.builder con SliverGridDelegateWithMaxCrossAxisExtent, che calcola automaticamente le colonne fissando una larghezza massima per ogni elemento.

    class GrigliaCard extends StatelessWidget {
      const GrigliaCard({super.key});
    
      @override
      Widget build(BuildContext context) {
        return GridView.builder(
          padding: const EdgeInsets.all(16),
          gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
            maxCrossAxisExtent: 250, // larghezza massima di ogni card
            mainAxisSpacing: 12,
            crossAxisSpacing: 12,
            childAspectRatio: 3 / 2,
          ),
          itemCount: 12,
          itemBuilder: (context, index) {
            return Card(
              elevation: 2,
              child: Center(child: Text('Elemento ${index + 1}')),
            );
          },
        );
      }
    }

    Risultato atteso

    La griglia mostra automaticamente più colonne su schermi larghi e meno su quelli stretti.

  5. 5

    Gestire righe flessibili con Expanded e Wrap

    Per disposizioni orizzontali, Expanded e Flexible distribuiscono lo spazio tra i figli di una Row. Quando però il contenuto rischia di traboccare su schermi piccoli, Wrap manda automaticamente gli elementi a capo. Combiniamo i due approcci usando LayoutResponsivo.

    class PannelloInfo extends StatelessWidget {
      const PannelloInfo({super.key});
    
      @override
      Widget build(BuildContext context) {
        final box1 = Container(height: 120, color: Colors.blue.shade100, child: const Center(child: Text('Box 1')));
        final box2 = Container(height: 120, color: Colors.green.shade100, child: const Center(child: Text('Box 2')));
        final box3 = Container(height: 120, color: Colors.orange.shade100, child: const Center(child: Text('Box 3')));
    
        return LayoutResponsivo(
          // Su mobile impiliamo in colonna
          mobile: Column(
            children: [box1, const SizedBox(height: 12), box2, const SizedBox(height: 12), box3],
          ),
          // Su tablet usiamo Wrap per andare a capo se serve
          tablet: Wrap(
            spacing: 12,
            runSpacing: 12,
            children: [
              SizedBox(width: 300, child: box1),
              SizedBox(width: 300, child: box2),
              SizedBox(width: 300, child: box3),
            ],
          ),
          // Su desktop distribuiamo lo spazio con Expanded
          desktop: Row(
            children: [
              Expanded(child: box1),
              const SizedBox(width: 12),
              Expanded(child: box2),
              const SizedBox(width: 12),
              Expanded(child: box3),
            ],
          ),
        );
      }
    }

    Risultato atteso

    I box si dispongono in colonna su mobile, a capo su tablet e affiancati su desktop.

  6. 6

    Assemblare la schermata finale

    Mettiamo insieme tutti i pezzi in una Scaffold. Combiniamo il PannelloInfo responsive con la GrigliaCard adattiva, usando MediaQuery per gestire eventuale padding di sistema e un titolo che cambia in base al dispositivo.

    class SchermataResponsive extends StatelessWidget {
      const SchermataResponsive({super.key});
    
      @override
      Widget build(BuildContext context) {
        final larghezza = MediaQuery.of(context).size.width;
        final tipo = tipoDispositivo(larghezza);
    
        return Scaffold(
          appBar: AppBar(
            title: Text('Layout ${tipo.name}'),
          ),
          body: SafeArea(
            child: Column(
              children: [
                const Padding(
                  padding: EdgeInsets.all(16),
                  child: PannelloInfo(),
                ),
                const Divider(),
                const Expanded(child: GrigliaCard()),
              ],
            ),
          ),
        );
      }
    }

    Risultato atteso

    Eseguendo l'app e ridimensionando la finestra (o ruotando il dispositivo) il layout si adatta dinamicamente.

  7. 7

    Best practice e consigli finali

    Alcuni accorgimenti per layout responsive solidi:

    • Preferisci LayoutBuilder a MediaQuery quando un widget non occupa tutto lo schermo (es. dentro un Drawer o un pannello).
    • Evita valori fissi di larghezza/altezza dove possibile: usa Expanded, Flexible e FractionallySizedBox.
    • Testa con il DevTools e con la modalità Resize su desktop/web per verificare i breakpoint.
    • Rispetta i SafeArea: usa il widget SafeArea per evitare notch e barre di sistema.
    • Considera MediaQuery.textScalerOf(context) per supportare l'accessibilità e i testi ingranditi.

    Con questi strumenti la tua app sarà pronta per girare bene su qualsiasi schermo.

    Risultato atteso

    Conosci le linee guida per mantenere il layout robusto su tutti i dispositivi.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!