[{"data":1,"prerenderedAt":85},["ShallowReactive",2],{"tutorial-spaziature-e-scroll-in-flutter-safearea-sizedbox-spacer-e-singlechildscrollview":3,"comments-tutorial-spaziature-e-scroll-in-flutter-safearea-sizedbox-spacer-e-singlechildscrollview":84},{"id":4,"title":5,"slug":6,"excerpt":7,"intro":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"video":16,"difficulty":20,"estimated_minutes":21,"flutter_version":22,"status":23,"published_at":24,"meta_title":25,"meta_description":26,"category":27,"author":31,"steps":33},111,"Spaziature e scroll in Flutter: SafeArea, SizedBox, Spacer e SingleChildScrollView","spaziature-e-scroll-in-flutter-safearea-sizedbox-spacer-e-singlechildscrollview","Impara a dare respiro alle tue schermate Flutter: SafeArea per notch e barre di sistema, SizedBox e Spacer per le spaziature, SingleChildScrollView per evitare l'overflow quando compare la tastiera.","Quando si costruisce la prima interfaccia in Flutter, il problema non è quasi mai \"come mostro un testo\", ma **come lo posiziono bene**: elementi troppo attaccati, contenuti nascosti sotto il notch, il famigerato nastro giallo e nero di overflow appena si apre la tastiera.\n\nIn questo tutorial costruiamo passo passo una schermata di profilo e impariamo a usare quattro widget semplicissimi ma fondamentali:\n\n- **SafeArea** per non finire sotto notch, barra di stato e home indicator;\n- **SizedBox** per creare spazi fissi e prevedibili;\n- **Spacer** (ed `Expanded`) per distribuire lo spazio rimasto;\n- **SingleChildScrollView** per rendere scorrevole una schermata che non ci sta nello schermo.\n\nNon serve nessun pacchetto esterno: tutto quello che useremo fa parte di Flutter. Al termine avrai una schermata pulita, che si comporta bene su telefoni piccoli e grandi e che non va in overflow quando l'utente scrive in un campo di testo.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002F6fb4bcb9-4680-4afa-ab16-cd890ee7942d.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1664883410931-2edffc8be5e6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3OTA4MjkwNzZ8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Mediamodifier","https:\u002F\u002Funsplash.com\u002F@mediamodifier","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fa-white-cell-phone-dBESAHgPL6c","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=OT6FMN83n5M",{"youtube_id":17,"duration_seconds":18,"published_at":19},"OT6FMN83n5M",142,"2026-10-01T11:17:47+00:00","beginner",30,"3.x","published","2026-10-01T04:31:16+00:00","SafeArea, SizedBox, Spacer e scroll in Flutter","Guida beginner alle spaziature in Flutter: SafeArea, SizedBox, Spacer, Padding e SingleChildScrollView per layout ordinati e senza overflow.",{"id":28,"name":29,"slug":30},1,"Guide","guide",{"id":28,"name":32},"Flutter Bot",[34,42,49,56,63,70,77],{"id":35,"position":28,"title":36,"body":37,"code_snippet":38,"code_language":39,"expected_result":40,"demo_url":41,"video_url":41},742,"Creare la schermata di partenza","Partiamo da un progetto Flutter vuoto (`flutter create spazi_demo`) e sostituiamo il contenuto di `lib\u002Fmain.dart`.\n\nCreiamo una semplice schermata di profilo con un avatar, un nome e una breve descrizione, dentro una `Column`. Per ora **non** aggiungiamo spaziature: vogliamo vedere il problema prima di risolverlo.\n\nNota la proprietà `crossAxisAlignment: CrossAxisAlignment.stretch`: fa sì che i figli della `Column` occupino tutta la larghezza disponibile, utile per i bottoni.","import 'package:flutter\u002Fmaterial.dart';\n\nvoid main() => runApp(const MyApp());\n\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      title: 'Spazi Demo',\n      theme: ThemeData(\n        colorSchemeSeed: Colors.indigo,\n        useMaterial3: true,\n      ),\n      home: const ProfiloPage(),\n    );\n  }\n}\n\nclass ProfiloPage extends StatelessWidget {\n  const ProfiloPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Column(\n        crossAxisAlignment: CrossAxisAlignment.stretch,\n        children: const [\n          CircleAvatar(\n            radius: 48,\n            child: Icon(Icons.person, size: 48),\n          ),\n          Text(\n            'Giulia Rossi',\n            textAlign: TextAlign.center,\n            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),\n          ),\n          Text(\n            'Flutter developer a Milano',\n            textAlign: TextAlign.center,\n          ),\n        ],\n      ),\n    );\n  }\n}","dart","L'app si avvia e mostra avatar, nome e descrizione tutti attaccati l'uno all'altro, con il contenuto che parte dal bordo superiore dello schermo (in parte sotto la barra di stato).",null,{"id":43,"position":44,"title":45,"body":46,"code_snippet":47,"code_language":39,"expected_result":48,"demo_url":41,"video_url":41},743,2,"Proteggere il contenuto con SafeArea","Il primo problema è evidente: il contenuto finisce **sotto la barra di stato** (e sui telefoni con notch può essere coperto).\n\n`SafeArea` è un widget che aggiunge automaticamente il padding necessario per evitare le zone \"non sicure\" dello schermo: barra di stato, notch, home indicator di iOS.\n\nSi usa semplicemente avvolgendo il contenuto:\n\n```dart\nSafeArea(child: ...)\n```\n\nPuoi anche disattivare singoli lati, ad esempio `SafeArea(top: false, child: ...)` se sopra hai già una `AppBar` (che gestisce da sola l'area sicura).\n\nAggiungiamo anche un `Padding` orizzontale di 24 pixel per staccare il contenuto dai bordi laterali: è la regola d'oro di ogni layout leggibile.","@override\nWidget build(BuildContext context) {\n  return Scaffold(\n    body: SafeArea(\n      child: Padding(\n        padding: const EdgeInsets.symmetric(horizontal: 24),\n        child: Column(\n          crossAxisAlignment: CrossAxisAlignment.stretch,\n          children: const [\n            CircleAvatar(\n              radius: 48,\n              child: Icon(Icons.person, size: 48),\n            ),\n            Text(\n              'Giulia Rossi',\n              textAlign: TextAlign.center,\n              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),\n            ),\n            Text(\n              'Flutter developer a Milano',\n              textAlign: TextAlign.center,\n            ),\n          ],\n        ),\n      ),\n    ),\n  );\n}","Il contenuto non è più sotto la barra di stato ed è staccato di 24 pixel dai bordi laterali. Gli elementi sono però ancora incollati verticalmente.",{"id":50,"position":51,"title":52,"body":53,"code_snippet":54,"code_language":39,"expected_result":55,"demo_url":41,"video_url":41},744,3,"SizedBox: spazi fissi e prevedibili","Per separare verticalmente i widget di una `Column` il modo più idiomatico in Flutter è inserire dei `SizedBox` con un'altezza fissa.\n\n`SizedBox` è un widget che occupa semplicemente lo spazio che gli dici:\n\n- `SizedBox(height: 16)` → spazio verticale di 16 px;\n- `SizedBox(width: 8)` → spazio orizzontale di 8 px (dentro una `Row`);\n- `SizedBox(height: 48, child: ElevatedButton(...))` → forza l'altezza di un figlio.\n\nUsa sempre `const` davanti a `SizedBox`: è un widget immutabile e Flutter può riutilizzarlo senza ricostruirlo.\n\n**Suggerimento pratico:** lavora con multipli di 4 o 8 (4, 8, 16, 24, 32). Mantiene il design coerente ed è la base dei sistemi di spaziatura di Material Design.\n\nAggiungiamo anche una `Row` con due statistiche, separate da uno `SizedBox` orizzontale.","Column(\n  crossAxisAlignment: CrossAxisAlignment.stretch,\n  children: [\n    const SizedBox(height: 32),\n    const CircleAvatar(\n      radius: 48,\n      child: Icon(Icons.person, size: 48),\n    ),\n    const SizedBox(height: 16),\n    const Text(\n      'Giulia Rossi',\n      textAlign: TextAlign.center,\n      style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),\n    ),\n    const SizedBox(height: 8),\n    const Text(\n      'Flutter developer a Milano',\n      textAlign: TextAlign.center,\n    ),\n    const SizedBox(height: 24),\n    Row(\n      mainAxisAlignment: MainAxisAlignment.center,\n      children: const [\n        _Statistica(valore: '128', etichetta: 'Progetti'),\n        SizedBox(width: 32),\n        _Statistica(valore: '2.4k', etichetta: 'Follower'),\n      ],\n    ),\n  ],\n)","Gli elementi sono ora distanziati in modo regolare e leggibile. Devi ancora creare il widget `_Statistica`, che vedremo nel passo successivo.",{"id":57,"position":58,"title":59,"body":60,"code_snippet":61,"code_language":39,"expected_result":62,"demo_url":41,"video_url":41},745,4,"Creare il piccolo widget _Statistica","Per tenere il codice ordinato estraiamo le statistiche in un widget privato (il prefisso `_` lo rende visibile solo in questo file).\n\nQui usiamo di nuovo `SizedBox` per separare il numero dall'etichetta e un `Column` con `mainAxisSize: MainAxisSize.min`, che dice alla colonna di occupare **solo lo spazio necessario** invece di espandersi in verticale. È un dettaglio importante: dentro una `Row`, una `Column` senza `mainAxisSize.min` tenta di prendere tutta l'altezza disponibile.\n\nAggiungi questa classe in fondo a `main.dart`.","class _Statistica extends StatelessWidget {\n  const _Statistica({required this.valore, required this.etichetta});\n\n  final String valore;\n  final String etichetta;\n\n  @override\n  Widget build(BuildContext context) {\n    return Column(\n      mainAxisSize: MainAxisSize.min,\n      children: [\n        Text(\n          valore,\n          style: const TextStyle(\n            fontSize: 20,\n            fontWeight: FontWeight.bold,\n          ),\n        ),\n        const SizedBox(height: 4),\n        Text(\n          etichetta,\n          style: TextStyle(color: Colors.grey.shade600),\n        ),\n      ],\n    );\n  }\n}","Sotto la descrizione compaiono due blocchi di statistiche affiancati e centrati, con numero in grassetto ed etichetta grigia.",{"id":64,"position":65,"title":66,"body":67,"code_snippet":68,"code_language":39,"expected_result":69,"demo_url":41,"video_url":41},746,5,"Spacer: distribuire lo spazio che avanza","`SizedBox` crea spazi **fissi**. Ma se volessimo \"spingere\" un bottone in fondo allo schermo, qualunque sia l'altezza del dispositivo?\n\nQui entra in gioco `Spacer`: è un widget che occupa tutto lo spazio libero rimasto lungo l'asse principale. In pratica è una scorciatoia per `Expanded(child: SizedBox())`.\n\nRegole da ricordare:\n\n- funziona solo dentro `Row`, `Column` o `Flex`;\n- se ci sono più `Spacer`, lo spazio si divide in base al parametro `flex` (default 1);\n- **non** si può usare dentro uno scroll (lo vedremo nel prossimo passo): lì lo spazio disponibile è infinito e Flutter solleva un errore.\n\nAggiungiamo in fondo alla `Column` uno `Spacer` e un bottone.","Column(\n  crossAxisAlignment: CrossAxisAlignment.stretch,\n  children: [\n    const SizedBox(height: 32),\n    \u002F\u002F ... avatar, nome, descrizione, statistiche ...\n    const Spacer(), \u002F\u002F spinge il bottone verso il fondo\n    FilledButton(\n      onPressed: () {},\n      child: const Padding(\n        padding: EdgeInsets.symmetric(vertical: 12),\n        child: Text('Modifica profilo'),\n      ),\n    ),\n    const SizedBox(height: 16),\n    OutlinedButton(\n      onPressed: () {},\n      child: const Padding(\n        padding: EdgeInsets.symmetric(vertical: 12),\n        child: Text('Condividi'),\n      ),\n    ),\n    const SizedBox(height: 24),\n  ],\n)","I due bottoni sono ancorati nella parte bassa dello schermo e occupano tutta la larghezza disponibile, mentre il profilo resta in alto.",{"id":71,"position":72,"title":73,"body":74,"code_snippet":75,"code_language":39,"expected_result":76,"demo_url":41,"video_url":41},747,6,"Evitare l'overflow con SingleChildScrollView","Prova a ruotare il dispositivo in orizzontale o ad aprire l'app su uno schermo piccolo: comparirà il classico nastro giallo e nero con il messaggio `A RenderFlex overflowed by X pixels`.\n\nLa soluzione è rendere la pagina scorrevole con `SingleChildScrollView`, che permette al suo unico figlio di essere più alto dello schermo.\n\nAttenzione però: **dentro uno scroll non puoi usare `Spacer` o `Expanded`**, perché l'altezza disponibile diventa illimitata. La tecnica standard per avere \"scroll quando serve, bottoni in fondo quando c'è spazio\" è combinare `LayoutBuilder`, `ConstrainedBox` e `IntrinsicHeight`.\n\nIn alternativa, se vuoi la massima semplicità, sostituisci lo `Spacer` con un `SizedBox(height: 32)` e avvolgi tutto in `SingleChildScrollView`. Qui vediamo la versione completa e robusta.","@override\nWidget build(BuildContext context) {\n  return Scaffold(\n    body: SafeArea(\n      child: LayoutBuilder(\n        builder: (context, constraints) {\n          return SingleChildScrollView(\n            padding: const EdgeInsets.symmetric(horizontal: 24),\n            child: ConstrainedBox(\n              constraints: BoxConstraints(\n                minHeight: constraints.maxHeight,\n              ),\n              child: IntrinsicHeight(\n                child: Column(\n                  crossAxisAlignment: CrossAxisAlignment.stretch,\n                  children: [\n                    const SizedBox(height: 32),\n                    const CircleAvatar(\n                      radius: 48,\n                      child: Icon(Icons.person, size: 48),\n                    ),\n                    const SizedBox(height: 16),\n                    const Text(\n                      'Giulia Rossi',\n                      textAlign: TextAlign.center,\n                      style: TextStyle(\n                          fontSize: 24, fontWeight: FontWeight.bold),\n                    ),\n                    const SizedBox(height: 8),\n                    const Text(\n                      'Flutter developer a Milano',\n                      textAlign: TextAlign.center,\n                    ),\n                    const SizedBox(height: 24),\n                    Row(\n                      mainAxisAlignment: MainAxisAlignment.center,\n                      children: const [\n                        _Statistica(valore: '128', etichetta: 'Progetti'),\n                        SizedBox(width: 32),\n                        _Statistica(valore: '2.4k', etichetta: 'Follower'),\n                      ],\n                    ),\n                    const SizedBox(height: 24),\n                    const TextField(\n                      decoration: InputDecoration(\n                        labelText: 'Bio',\n                        border: OutlineInputBorder(),\n                      ),\n                    ),\n                    const Spacer(),\n                    FilledButton(\n                      onPressed: () {},\n                      child: const Padding(\n                        padding: EdgeInsets.symmetric(vertical: 12),\n                        child: Text('Modifica profilo'),\n                      ),\n                    ),\n                    const SizedBox(height: 16),\n                    OutlinedButton(\n                      onPressed: () {},\n                      child: const Padding(\n                        padding: EdgeInsets.symmetric(vertical: 12),\n                        child: Text('Condividi'),\n                      ),\n                    ),\n                    const SizedBox(height: 24),\n                  ],\n                ),\n              ),\n            ),\n          );\n        },\n      ),\n    ),\n  );\n}","Su schermi grandi i bottoni restano in basso grazie allo Spacer; su schermi piccoli o quando si apre la tastiera la pagina diventa scorrevole e il nastro di overflow sparisce.",{"id":78,"position":79,"title":80,"body":81,"code_snippet":82,"code_language":39,"expected_result":83,"demo_url":41,"video_url":41},748,7,"Rifinire con Container, Padding e margin","Ultimo tassello: la differenza tra `Padding` e `Container`.\n\n- `Padding` aggiunge **solo** spazio interno attorno al figlio. È leggero e va preferito quando ti serve soltanto quello.\n- `Container` è un widget \"coltellino svizzero\": può avere `padding`, `margin`, `color`, `decoration`, dimensioni fisse. Usalo quando ti serve più del semplice spazio.\n\nRicorda la distinzione: **padding = spazio dentro il bordo**, **margin = spazio fuori dal bordo**.\n\nAvvolgiamo la riga delle statistiche in un `Container` decorato per vedere la differenza all'opera: il `padding` allontana i numeri dal bordo grigio, il `margin` allontana il riquadro dagli altri widget.","Container(\n  margin: const EdgeInsets.symmetric(vertical: 8),\n  padding: const EdgeInsets.symmetric(vertical: 16),\n  decoration: BoxDecoration(\n    color: Colors.indigo.withOpacity(0.06),\n    borderRadius: BorderRadius.circular(16),\n  ),\n  child: Row(\n    mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n    children: const [\n      _Statistica(valore: '128', etichetta: 'Progetti'),\n      _Statistica(valore: '2.4k', etichetta: 'Follower'),\n      _Statistica(valore: '87', etichetta: 'Seguiti'),\n    ],\n  ),\n)","Le statistiche appaiono dentro un riquadro con sfondo tenue e angoli arrotondati, distanziate automaticamente grazie a MainAxisAlignment.spaceEvenly e staccate dagli altri elementi dal margin.",[],1792316928393]