Layout in Flutter con Row, Column, Expanded, Padding e Container

Foto di MChe Lee su Unsplash

GuidePrincipiante35 min Flutter 3.x

Layout in Flutter con Row, Column, Expanded, Padding e Container

In Flutter tutto è un widget, e i layout non fanno eccezione: non esistono file XML o storyboard, ma widget che si annidano l'uno dentro l'altro per comporre l'interfaccia.

I mattoni fondamentali sono pochissimi e imparandoli bene puoi costruire praticamente qualsiasi schermata:

  • Column → dispone i figli in verticale
  • Row → dispone i figli in orizzontale
  • Expanded / Flexible → distribuiscono lo spazio disponibile
  • Padding → aggiunge spazio interno attorno a un widget
  • Container → il "coltellino svizzero": dimensioni, margini, colori, bordi, ombre

In questa guida partiremo da un progetto vuoto e arriveremo passo dopo passo a una card profilo completa, imparando anche a riconoscere e risolvere il temutissimo errore giallo e nero di overflow.

Prerequisiti: Flutter installato e un progetto creato con flutter create layout_demo. Nessuna libreria esterna richiesta.

  1. 1

    Creare lo scheletro dell'app con MaterialApp e Scaffold

    Apri il file lib/main.dart e sostituisci tutto il contenuto con un'app minimale.

    Ogni schermata parte quasi sempre da uno Scaffold, che fornisce la struttura base di una pagina Material: appBar, body, eventuale floatingActionButton.

    Il body conterrà i nostri esperimenti di layout. Nota il widget Center: senza di esso il figlio si posiziona in alto a sinistra.

    Qualche concetto chiave da fissare subito:

    • lo Scaffold occupa tutto lo schermo;
    • il body riceve dei vincoli (constraints) e li passa ai figli;
    • un widget in Flutter dice al genitore quanto spazio vuole, ma è il genitore a decidere quanto gliene dà.
    import 'package:flutter/material.dart';
    
    void main() => runApp(const LayoutDemoApp());
    
    class LayoutDemoApp extends StatelessWidget {
      const LayoutDemoApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Layout Demo',
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            colorSchemeSeed: Colors.indigo,
            useMaterial3: true,
          ),
          home: const HomePage(),
        );
      }
    }
    
    class HomePage extends StatelessWidget {
      const HomePage({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Layout in Flutter')),
          body: const Center(
            child: Text('Iniziamo!'),
          ),
        );
      }
    }

    Risultato atteso

    Avviando l'app con `flutter run` vedi una AppBar indaco con il titolo "Layout in Flutter" e la scritta "Iniziamo!" al centro dello schermo.

  2. 2

    Impilare i widget in verticale con Column

    Column dispone i figli uno sotto l'altro. I due parametri che userai di più sono:

    • mainAxisAlignment → allineamento lungo l'asse principale (per Column: verticale)
    • crossAxisAlignment → allineamento lungo l'asse trasversale (per Column: orizzontale)

    Valori tipici di mainAxisAlignment: start, center, end, spaceBetween, spaceAround, spaceEvenly.

    Valori tipici di crossAxisAlignment: start, center, end, stretch.

    Un altro parametro utile è mainAxisSize: di default vale MainAxisSize.max, cioè la Column occupa tutta l'altezza disponibile. Con MainAxisSize.min si restringe al contenuto (fondamentale quando la Column sta dentro una Card o un bottom sheet).

    Sostituisci il body dello Scaffold con il codice qui sotto e prova a cambiare i valori di allineamento salvando con hot reload (r da terminale, o salva da IDE).

    body: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Primo', style: Theme.of(context).textTheme.headlineSmall),
        const SizedBox(height: 12), // spaziatore verticale
        const Text('Secondo'),
        const SizedBox(height: 12),
        const Text('Terzo'),
      ],
    ),

    Risultato atteso

    Tre testi impilati verticalmente, centrati in altezza e allineati a sinistra. Cambiando `crossAxisAlignment` in `center` i testi si spostano al centro orizzontale.

  3. 3

    Disporre in orizzontale con Row e distribuire lo spazio con Expanded

    Row funziona esattamente come Column, ma l'asse principale è orizzontale. Il problema classico è che i figli di una Row prendono la larghezza che vogliono: se sono troppo larghi, ecco l'overflow.

    La soluzione è Expanded, che dice al figlio: "prenditi tutto lo spazio rimasto". Con il parametro flex decidi le proporzioni: due Expanded con flex: 1 e flex: 2 si dividono lo spazio in 1/3 e 2/3.

    Differenze da ricordare:

    Widget Comportamento
    Expanded forza il figlio a riempire lo spazio assegnato
    Flexible permette al figlio di essere più piccolo dello spazio assegnato
    Spacer uno spazio vuoto elastico (è un Expanded con figlio vuoto)

    Sostituisci il body con l'esempio seguente.

    body: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          // Proporzioni 1:2:1
          Row(
            children: [
              Expanded(
                flex: 1,
                child: Container(height: 80, color: Colors.red.shade300),
              ),
              Expanded(
                flex: 2,
                child: Container(height: 80, color: Colors.green.shade300),
              ),
              Expanded(
                flex: 1,
                child: Container(height: 80, color: Colors.blue.shade300),
              ),
            ],
          ),
          const SizedBox(height: 24),
          // Spacer: spinge il secondo elemento a destra
          Row(
            children: const [
              Icon(Icons.person, size: 32),
              SizedBox(width: 8),
              Text('Mario Rossi'),
              Spacer(),
              Icon(Icons.chevron_right),
            ],
          ),
        ],
      ),
    ),

    Risultato atteso

    In alto tre blocchi colorati che occupano tutta la larghezza con proporzioni 1:2:1; sotto una riga con icona e nome a sinistra e una freccia spinta all'estrema destra.

  4. 4

    Rifinire con Padding, Container e BoxDecoration

    Padding aggiunge spazio dentro il genitore, attorno al figlio. Si usa con EdgeInsets:

    • EdgeInsets.all(16) → 16 px su tutti i lati
    • EdgeInsets.symmetric(horizontal: 16, vertical: 8)
    • EdgeInsets.only(left: 12, top: 4)

    Container è il widget tuttofare: puoi impostare width, height, color, padding, margin, alignment e soprattutto decoration.

    ⚠️ Attenzione: non puoi usare contemporaneamente color e decoration. Se usi BoxDecoration, il colore va dichiarato dentro la decoration.

    Differenza fra padding e margin del Container: il padding è lo spazio interno (tra bordo e contenuto), il margin è lo spazio esterno (tra il Container e i widget vicini).

    Se ti serve solo spazio interno, preferisci Padding a un Container: è più leggero e più leggibile.

    body: Center(
      child: Container(
        margin: const EdgeInsets.symmetric(horizontal: 24),
        padding: const EdgeInsets.all(20),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(16),
          border: Border.all(color: Colors.indigo.shade100),
          boxShadow: [
            BoxShadow(
              color: Colors.black.withOpacity(0.08),
              blurRadius: 12,
              offset: const Offset(0, 4),
            ),
          ],
        ),
        child: Column(
          mainAxisSize: MainAxisSize.min, // si restringe al contenuto
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Container decorato',
              style: Theme.of(context).textTheme.titleLarge,
            ),
            const SizedBox(height: 8),
            const Text(
              'Padding interno, margine esterno, angoli arrotondati e ombra.',
            ),
          ],
        ),
      ),
    ),

    Risultato atteso

    Una scheda bianca centrata, con angoli arrotondati, bordo sottile e una leggera ombra sotto. Il testo non tocca i bordi grazie al padding di 20 px.

  5. 5

    Comporre il tutto: una card profilo con Row e Column annidate

    Adesso mettiamo insieme i pezzi. Il segreto dei layout Flutter è annidare: una Row che contiene un'immagine e, accanto, una Column con i testi.

    Schema mentale della card:

    Card
     └── Padding
          └── Row
               ├── CircleAvatar
               ├── SizedBox (spazio)
               ├── Expanded
               │    └── Column (nome, ruolo, città)
               └── IconButton
    

    L'Expanded attorno alla Column è essenziale: senza di esso, se il nome è lungo, la Row va in overflow. Con Expanded la Column riceve una larghezza massima e il testo può andare a capo o essere troncato con overflow: TextOverflow.ellipsis.

    Crea un widget separato ProfileCard per mantenere il codice pulito e riutilizzabile.

    class ProfileCard extends StatelessWidget {
      const ProfileCard({
        super.key,
        required this.name,
        required this.role,
        required this.city,
      });
    
      final String name;
      final String role;
      final String city;
    
      @override
      Widget build(BuildContext context) {
        final textTheme = Theme.of(context).textTheme;
    
        return Card(
          margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                CircleAvatar(
                  radius: 28,
                  backgroundColor: Colors.indigo.shade100,
                  child: Text(
                    name.characters.first,
                    style: textTheme.titleLarge,
                  ),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Text(
                        name,
                        style: textTheme.titleMedium,
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                      ),
                      const SizedBox(height: 4),
                      Text(role, style: textTheme.bodyMedium),
                      const SizedBox(height: 4),
                      Row(
                        children: [
                          const Icon(Icons.place, size: 16),
                          const SizedBox(width: 4),
                          Flexible(
                            child: Text(
                              city,
                              overflow: TextOverflow.ellipsis,
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
                IconButton(
                  icon: const Icon(Icons.more_vert),
                  onPressed: () {},
                ),
              ],
            ),
          ),
        );
      }
    }

    Risultato atteso

    Usando `ProfileCard(name: 'Giulia Bianchi', role: 'Flutter Developer', city: 'Bologna')` nel body vedi una card con avatar circolare, tre righe di testo e un menu a tre puntini a destra.

  6. 6

    Evitare l'overflow: ListView, SingleChildScrollView e SafeArea

    Se metti tante card dentro una Column, prima o poi comparirà la classica banda gialla e nera "BOTTOM OVERFLOWED BY X PIXELS": la Column non scorre, quindi il contenuto esce dallo schermo.

    Le soluzioni, in ordine di preferenza:

    1. ListView (o ListView.builder) quando gli elementi sono molti o dinamici: scorre nativamente ed è efficiente.
    2. SingleChildScrollView + Column quando hai una schermata statica ma lunga (es. un form).
    3. Expanded / Flexible quando l'overflow nasce da un figlio troppo grande dentro Row o Column.

    Usa inoltre SafeArea per non finire sotto la notch o la barra di sistema.

    Errore da evitare: mettere un ListView dentro una Column senza vincoli di altezza → errore "Vertical viewport was given unbounded height". La soluzione è avvolgere il ListView in un Expanded, oppure usare shrinkWrap: true con physics: NeverScrollableScrollPhysics() se gli elementi sono pochi.

    Ecco la versione finale della HomePage.

    class HomePage extends StatelessWidget {
      const HomePage({super.key});
    
      static const _people = [
        ('Giulia Bianchi', 'Flutter Developer', 'Bologna'),
        ('Marco Verdi', 'UI/UX Designer', 'Milano'),
        ('Sara Neri', 'Backend Engineer', 'Napoli'),
        ('Luca Gialli', 'QA Specialist', 'Torino'),
        ('Anna Blu', 'Product Owner', 'Roma'),
      ];
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Team')),
          body: SafeArea(
            child: Column(
              children: [
                Padding(
                  padding: const EdgeInsets.all(16),
                  child: Row(
                    children: [
                      Expanded(
                        child: Text(
                          'Membri del team',
                          style: Theme.of(context).textTheme.titleLarge,
                        ),
                      ),
                      Text('${_people.length}'),
                    ],
                  ),
                ),
                // Expanded dà un'altezza finita al ListView
                Expanded(
                  child: ListView.builder(
                    itemCount: _people.length,
                    itemBuilder: (context, index) {
                      final (name, role, city) = _people[index];
                      return ProfileCard(name: name, role: role, city: city);
                    },
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }

    Risultato atteso

    Una lista scorrevole di card profilo con un'intestazione fissa in alto, senza alcun messaggio di overflow anche ruotando il dispositivo in orizzontale.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!