[{"data":1,"prerenderedAt":68},["ShallowReactive",2],{"tutorial-bottom-sheet-persistenti-e-modali-in-flutter-con-showmodalbottomsheet-e-draggablescrollablesheet":3,"comments-tutorial-bottom-sheet-persistenti-e-modali-in-flutter-con-showmodalbottomsheet-e-draggablescrollablesheet":67},{"id":4,"title":5,"slug":6,"excerpt":7,"intro":8,"cover_image":9,"video_url":10,"difficulty":11,"estimated_minutes":12,"flutter_version":13,"status":14,"published_at":15,"meta_title":16,"meta_description":17,"category":18,"author":22,"steps":24},28,"Bottom sheet persistenti e modali in Flutter con showModalBottomSheet e DraggableScrollableSheet","bottom-sheet-persistenti-e-modali-in-flutter-con-showmodalbottomsheet-e-draggablescrollablesheet","Impara a creare bottom sheet modali e trascinabili in Flutter, gestendo altezza dinamica, contenuto scorrevole e valori di ritorno per migliorare la UX della tua app.","I bottom sheet sono un elemento fondamentale del Material Design: pannelli che scivolano dal basso dello schermo per mostrare azioni contestuali o contenuti aggiuntivi senza cambiare schermata.\n\nIn questo tutorial vedremo come usare `showModalBottomSheet` per i sheet modali, come restituire un valore alla schermata chiamante e come rendere il contenuto trascinabile e scorrevole con `DraggableScrollableSheet`. Al termine avrai una serie di pattern riutilizzabili per arricchire la UX delle tue app.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002Ff2e973a6-8182-467f-a745-4ba09ce8a9d2.jpg",null,"intermediate",35,"3.x","published","2026-07-04T04:30:51+00:00","Bottom sheet in Flutter: modali e trascinabili","Guida pratica ai bottom sheet in Flutter con showModalBottomSheet e DraggableScrollableSheet: valori di ritorno, contenuto scorrevole e altezza dinamica.",{"id":19,"name":20,"slug":21},1,"Guide","guide",{"id":19,"name":23},"Flutter Bot",[25,32,39,46,53,60],{"id":26,"position":19,"title":27,"body":28,"code_snippet":29,"code_language":30,"expected_result":31,"demo_url":10,"video_url":10},189,"Preparare la schermata di base","Creiamo una semplice schermata con un pulsante centrale che useremo per aprire i vari tipi di bottom sheet. Partiamo da uno `Scaffold` minimale.","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: 'Bottom Sheet Demo',\n      theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),\n      home: const HomePage(),\n    );\n  }\n}\n\nclass HomePage extends StatelessWidget {\n  const HomePage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Bottom Sheet Demo')),\n      body: Center(\n        child: ElevatedButton(\n          onPressed: () {},\n          child: const Text('Apri bottom sheet'),\n        ),\n      ),\n    );\n  }\n}","dart","Una schermata con un pulsante centrale (ancora senza azione) e una AppBar.",{"id":33,"position":34,"title":35,"body":36,"code_snippet":37,"code_language":30,"expected_result":38,"demo_url":10,"video_url":10},190,2,"Aprire un bottom sheet modale semplice","Usiamo `showModalBottomSheet` per mostrare un pannello modale. Il parametro `builder` restituisce il contenuto del sheet. Aggiungiamo un padding e un `SafeArea` per rispettare le zone di sistema.\n\nNota il `mainAxisSize: MainAxisSize.min` sulla `Column`: fa sì che il sheet occupi solo lo spazio necessario.","void _mostraSheetSemplice(BuildContext context) {\n  showModalBottomSheet(\n    context: context,\n    builder: (context) {\n      return SafeArea(\n        child: Padding(\n          padding: const EdgeInsets.all(16),\n          child: Column(\n            mainAxisSize: MainAxisSize.min,\n            children: [\n              ListTile(\n                leading: const Icon(Icons.share),\n                title: const Text('Condividi'),\n                onTap: () => Navigator.pop(context),\n              ),\n              ListTile(\n                leading: const Icon(Icons.link),\n                title: const Text('Copia link'),\n                onTap: () => Navigator.pop(context),\n              ),\n              ListTile(\n                leading: const Icon(Icons.delete_outline),\n                title: const Text('Elimina'),\n                onTap: () => Navigator.pop(context),\n              ),\n            ],\n          ),\n        ),\n      );\n    },\n  );\n}","Collegando questa funzione al pulsante, appare dal basso un pannello con tre opzioni cliccabili.",{"id":40,"position":41,"title":42,"body":43,"code_snippet":44,"code_language":30,"expected_result":45,"demo_url":10,"video_url":10},191,3,"Restituire un valore alla schermata chiamante","`showModalBottomSheet` è asincrono e restituisce un `Future` con il valore passato a `Navigator.pop(context, valore)`. Questo permette di sapere quale opzione ha scelto l'utente.\n\nUsiamo `await` e mostriamo il risultato con uno `SnackBar`.","Future\u003Cvoid> _scegliOpzione(BuildContext context) async {\n  final scelta = await showModalBottomSheet\u003CString>(\n    context: context,\n    builder: (context) {\n      return SafeArea(\n        child: Column(\n          mainAxisSize: MainAxisSize.min,\n          children: [\n            ListTile(\n              title: const Text('Opzione A'),\n              onTap: () => Navigator.pop(context, 'A'),\n            ),\n            ListTile(\n              title: const Text('Opzione B'),\n              onTap: () => Navigator.pop(context, 'B'),\n            ),\n          ],\n        ),\n      );\n    },\n  );\n\n  if (scelta != null && context.mounted) {\n    ScaffoldMessenger.of(context).showSnackBar(\n      SnackBar(content: Text('Hai scelto: $scelta')),\n    );\n  }\n}","Dopo aver toccato un'opzione, il sheet si chiude e compare uno SnackBar con la scelta effettuata.",{"id":47,"position":48,"title":49,"body":50,"code_snippet":51,"code_language":30,"expected_result":52,"demo_url":10,"video_url":10},192,4,"Personalizzare forma, colore e comportamento","Possiamo personalizzare l'aspetto con `shape` (angoli arrotondati), `backgroundColor` e `isDismissible`. Con `isScrollControlled: true` permettiamo al sheet di superare metà schermo, utile per form.\n\nAggiungiamo anche un piccolo indicatore di trascinamento (grabber) in cima.","void _mostraSheetPersonalizzato(BuildContext context) {\n  showModalBottomSheet(\n    context: context,\n    isScrollControlled: true,\n    backgroundColor: Theme.of(context).colorScheme.surface,\n    shape: const RoundedRectangleBorder(\n      borderRadius: BorderRadius.vertical(top: Radius.circular(24)),\n    ),\n    builder: (context) {\n      return Padding(\n        \u002F\u002F Fondamentale per non far coprire i campi dalla tastiera\n        padding: EdgeInsets.only(\n          bottom: MediaQuery.of(context).viewInsets.bottom,\n        ),\n        child: Column(\n          mainAxisSize: MainAxisSize.min,\n          children: [\n            const SizedBox(height: 12),\n            Container(\n              width: 40,\n              height: 4,\n              decoration: BoxDecoration(\n                color: Colors.grey.shade400,\n                borderRadius: BorderRadius.circular(2),\n              ),\n            ),\n            const Padding(\n              padding: EdgeInsets.all(16),\n              child: TextField(\n                decoration: InputDecoration(\n                  labelText: 'Scrivi un commento',\n                  border: OutlineInputBorder(),\n                ),\n              ),\n            ),\n          ],\n        ),\n      );\n    },\n  );\n}","Un bottom sheet con angoli arrotondati, grabber in alto e un campo di testo che si solleva sopra la tastiera.",{"id":54,"position":55,"title":56,"body":57,"code_snippet":58,"code_language":30,"expected_result":59,"demo_url":10,"video_url":10},193,5,"Contenuto trascinabile con DraggableScrollableSheet","Quando il contenuto è lungo e vogliamo che l'utente possa espandere il sheet a schermo intero trascinandolo, usiamo `DraggableScrollableSheet`. \n\nI parametri `initialChildSize`, `minChildSize` e `maxChildSize` sono frazioni dell'altezza schermo (da 0 a 1). È essenziale collegare lo `scrollController` fornito al widget scorrevole interno, altrimenti il trascinamento non funziona correttamente.","void _mostraSheetTrascinabile(BuildContext context) {\n  showModalBottomSheet(\n    context: context,\n    isScrollControlled: true,\n    shape: const RoundedRectangleBorder(\n      borderRadius: BorderRadius.vertical(top: Radius.circular(24)),\n    ),\n    builder: (context) {\n      return DraggableScrollableSheet(\n        initialChildSize: 0.5,\n        minChildSize: 0.25,\n        maxChildSize: 0.9,\n        expand: false,\n        builder: (context, scrollController) {\n          return ListView.builder(\n            controller: scrollController, \u002F\u002F collegamento obbligatorio\n            itemCount: 40,\n            itemBuilder: (context, index) {\n              if (index == 0) {\n                return const Padding(\n                  padding: EdgeInsets.all(16),\n                  child: Text(\n                    'Trascina per espandere',\n                    style: TextStyle(fontWeight: FontWeight.bold),\n                  ),\n                );\n              }\n              return ListTile(\n                leading: CircleAvatar(child: Text('$index')),\n                title: Text('Elemento $index'),\n              );\n            },\n          );\n        },\n      );\n    },\n  );\n}","Un bottom sheet che parte a metà schermo e può essere trascinato fino al 90% dell'altezza, con lista scorrevole al suo interno.",{"id":61,"position":62,"title":63,"body":64,"code_snippet":65,"code_language":30,"expected_result":66,"demo_url":10,"video_url":10},194,6,"Collegare tutto e testare","Aggiorniamo la `HomePage` per mostrare tutti i pulsanti collegati alle funzioni create. Trasformiamo il widget in `StatefulWidget` non è necessario: bastano metodi che ricevono il `context`.\n\nUn accorgimento: dopo un `await` verifica sempre `context.mounted` prima di usare il `BuildContext`, per evitare errori se il widget è stato smontato.","class HomePage extends StatelessWidget {\n  const HomePage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Bottom Sheet Demo')),\n      body: Center(\n        child: Column(\n          mainAxisSize: MainAxisSize.min,\n          children: [\n            ElevatedButton(\n              onPressed: () => _mostraSheetSemplice(context),\n              child: const Text('Sheet semplice'),\n            ),\n            ElevatedButton(\n              onPressed: () => _scegliOpzione(context),\n              child: const Text('Sheet con valore di ritorno'),\n            ),\n            ElevatedButton(\n              onPressed: () => _mostraSheetPersonalizzato(context),\n              child: const Text('Sheet con campo di testo'),\n            ),\n            ElevatedButton(\n              onPressed: () => _mostraSheetTrascinabile(context),\n              child: const Text('Sheet trascinabile'),\n            ),\n          ],\n        ),\n      ),\n    );\n  }\n}","Quattro pulsanti che aprono rispettivamente i diversi tipi di bottom sheet realizzati durante il tutorial.",[],1785219609789]