[{"data":1,"prerenderedAt":73},["ShallowReactive",2],{"tutorial-costruire-una-ui-adattiva-per-desktop-e-web-in-flutter-con-navigationrail":3,"comments-tutorial-costruire-una-ui-adattiva-per-desktop-e-web-in-flutter-con-navigationrail":72},{"id":4,"title":5,"slug":6,"excerpt":7,"intro":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"video":15,"difficulty":16,"estimated_minutes":17,"flutter_version":18,"status":19,"published_at":20,"meta_title":21,"meta_description":22,"category":23,"author":27,"steps":29},68,"Costruire una UI adattiva per desktop e web in Flutter con NavigationRail","costruire-una-ui-adattiva-per-desktop-e-web-in-flutter-con-navigationrail","Impara a creare un'interfaccia adattiva che passa da NavigationBar su mobile a NavigationRail su schermi larghi, per app Flutter pronte per desktop e web.","Con Flutter puoi rilasciare la stessa app su mobile, desktop e web, ma una UI pensata solo per il telefono risulta scomoda su schermi ampi. In questo tutorial creeremo una **shell di navigazione adattiva** che mostra una `NavigationBar` in basso su dispositivi stretti e una `NavigationRail` laterale su schermi larghi. Useremo esclusivamente widget del framework, senza pacchetti esterni, applicando le linee guida di Material 3. Al termine avrai uno scaffold riutilizzabile per qualsiasi app multi-piattaforma.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002F562e824c-6cdb-4ffc-89a2-e6eb8ccd16c5.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1781032384657-320d38787e77?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODczMjczMjZ8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Josh Davies","https:\u002F\u002Funsplash.com\u002F@mestra","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fempty-light-wooden-shelf-on-a-cream-wall-Er2gvVJ8EiQ",null,"intermediate",35,"3.x","published","2026-08-19T04:30:44+00:00","UI adattiva Flutter con NavigationRail e NavigationBar","Crea una shell di navigazione adattiva in Flutter che passa da NavigationBar a NavigationRail su desktop e web. Guida pratica Material 3.",{"id":24,"name":25,"slug":26},1,"Guide","guide",{"id":24,"name":28},"Flutter Bot",[30,37,44,51,58,65],{"id":31,"position":24,"title":32,"body":33,"code_snippet":34,"code_language":35,"expected_result":36,"demo_url":15,"video_url":15},453,"Definire i destination della navigazione","Per evitare duplicazione tra `NavigationBar` e `NavigationRail`, definiamo una sola volta le voci di navigazione in una struttura dati condivisa. Creiamo una classe semplice con etichetta, icona e icona selezionata.","class NavDestinationData {\n  const NavDestinationData({\n    required this.label,\n    required this.icon,\n    required this.selectedIcon,\n  });\n\n  final String label;\n  final IconData icon;\n  final IconData selectedIcon;\n}\n\nconst destinations = \u003CNavDestinationData>[\n  NavDestinationData(\n    label: 'Home',\n    icon: Icons.home_outlined,\n    selectedIcon: Icons.home,\n  ),\n  NavDestinationData(\n    label: 'Cerca',\n    icon: Icons.search_outlined,\n    selectedIcon: Icons.search,\n  ),\n  NavDestinationData(\n    label: 'Profilo',\n    icon: Icons.person_outline,\n    selectedIcon: Icons.person,\n  ),\n];","dart","Una lista costante di destinazioni riutilizzabile da entrambi i widget di navigazione.",{"id":38,"position":39,"title":40,"body":41,"code_snippet":42,"code_language":35,"expected_result":43,"demo_url":15,"video_url":15},454,2,"Creare lo scaffold adattivo con LayoutBuilder","Usiamo un `LayoutBuilder` per decidere quale layout mostrare in base alla larghezza disponibile. Consideriamo una soglia di **600px**: sotto usiamo la `NavigationBar` in basso, sopra la `NavigationRail` a sinistra. Manteniamo l'indice selezionato in uno `StatefulWidget`.","class AdaptiveScaffold extends StatefulWidget {\n  const AdaptiveScaffold({super.key, required this.pages});\n\n  final List\u003CWidget> pages;\n\n  @override\n  State\u003CAdaptiveScaffold> createState() => _AdaptiveScaffoldState();\n}\n\nclass _AdaptiveScaffoldState extends State\u003CAdaptiveScaffold> {\n  int _selectedIndex = 0;\n\n  void _onSelect(int index) {\n    setState(() => _selectedIndex = index);\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return LayoutBuilder(\n      builder: (context, constraints) {\n        final isWide = constraints.maxWidth >= 600;\n        final body = widget.pages[_selectedIndex];\n\n        if (isWide) {\n          return _buildWideLayout(body);\n        }\n        return _buildNarrowLayout(body);\n      },\n    );\n  }\n}","Lo scaffold sa distinguere tra schermo stretto e largo, ma i due layout vanno ancora implementati.",{"id":45,"position":46,"title":47,"body":48,"code_snippet":49,"code_language":35,"expected_result":50,"demo_url":15,"video_url":15},455,3,"Layout stretto con NavigationBar","Su schermi stretti (mobile) usiamo la classica `NavigationBar` di Material 3 posizionata in basso. Convertiamo le nostre `NavDestinationData` in `NavigationDestination`.","Widget _buildNarrowLayout(Widget body) {\n  return Scaffold(\n    appBar: AppBar(title: Text(destinations[_selectedIndex].label)),\n    body: body,\n    bottomNavigationBar: NavigationBar(\n      selectedIndex: _selectedIndex,\n      onDestinationSelected: _onSelect,\n      destinations: [\n        for (final d in destinations)\n          NavigationDestination(\n            icon: Icon(d.icon),\n            selectedIcon: Icon(d.selectedIcon),\n            label: d.label,\n          ),\n      ],\n    ),\n  );\n}","Riducendo la finestra sotto i 600px compare la barra di navigazione inferiore.",{"id":52,"position":53,"title":54,"body":55,"code_snippet":56,"code_language":35,"expected_result":57,"demo_url":15,"video_url":15},456,4,"Layout largo con NavigationRail","Su schermi larghi usiamo una `NavigationRail` laterale affiancata al contenuto tramite una `Row`. La proprietà `labelType` mostra le etichette sotto le icone. Aggiungiamo un `VerticalDivider` come separatore visivo e avvolgiamo il contenuto in `Expanded`.","Widget _buildWideLayout(Widget body) {\n  return Scaffold(\n    body: Row(\n      children: [\n        NavigationRail(\n          selectedIndex: _selectedIndex,\n          onDestinationSelected: _onSelect,\n          labelType: NavigationRailLabelType.all,\n          leading: const Padding(\n            padding: EdgeInsets.symmetric(vertical: 16),\n            child: FlutterLogo(size: 32),\n          ),\n          destinations: [\n            for (final d in destinations)\n              NavigationRailDestination(\n                icon: Icon(d.icon),\n                selectedIcon: Icon(d.selectedIcon),\n                label: Text(d.label),\n              ),\n          ],\n        ),\n        const VerticalDivider(thickness: 1, width: 1),\n        Expanded(\n          child: Scaffold(\n            appBar: AppBar(title: Text(destinations[_selectedIndex].label)),\n            body: body,\n          ),\n        ),\n      ],\n    ),\n  );\n}","Allargando la finestra oltre 600px la navigazione si sposta lateralmente con la rail.",{"id":59,"position":60,"title":61,"body":62,"code_snippet":63,"code_language":35,"expected_result":64,"demo_url":15,"video_url":15},457,5,"Rendere la rail estesa su schermi molto larghi","Su desktop con molto spazio (oltre 1000px) possiamo mostrare la rail **estesa** con etichette accanto alle icone usando `extended: true`. Aggiorniamo `_buildWideLayout` per accettare un parametro e leggere la larghezza dal `LayoutBuilder`.","\u002F\u002F Nel build principale, passa la larghezza:\nif (isWide) {\n  return _buildWideLayout(body, extended: constraints.maxWidth >= 1000);\n}\n\n\u002F\u002F Aggiorna la firma e la NavigationRail:\nWidget _buildWideLayout(Widget body, {required bool extended}) {\n  return Scaffold(\n    body: Row(\n      children: [\n        NavigationRail(\n          extended: extended,\n          selectedIndex: _selectedIndex,\n          onDestinationSelected: _onSelect,\n          labelType: extended ? null : NavigationRailLabelType.all,\n          leading: const Padding(\n            padding: EdgeInsets.symmetric(vertical: 16),\n            child: FlutterLogo(size: 32),\n          ),\n          destinations: [\n            for (final d in destinations)\n              NavigationRailDestination(\n                icon: Icon(d.icon),\n                selectedIcon: Icon(d.selectedIcon),\n                label: Text(d.label),\n              ),\n          ],\n        ),\n        const VerticalDivider(thickness: 1, width: 1),\n        Expanded(\n          child: Scaffold(\n            appBar: AppBar(title: Text(destinations[_selectedIndex].label)),\n            body: body,\n          ),\n        ),\n      ],\n    ),\n  );\n}","Oltre i 1000px la rail si espande mostrando le etichette accanto alle icone, come nelle app desktop moderne.",{"id":66,"position":67,"title":68,"body":69,"code_snippet":70,"code_language":35,"expected_result":71,"demo_url":15,"video_url":15},458,6,"Collegare tutto nell'app","Infine forniamo le pagine reali allo scaffold e attiviamo Material 3 nel `ThemeData`. Ogni pagina è un semplice widget di contenuto; puoi sostituirle con le tue schermate.","void 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: 'UI Adattiva',\n      theme: ThemeData(\n        useMaterial3: true,\n        colorSchemeSeed: Colors.indigo,\n      ),\n      home: AdaptiveScaffold(\n        pages: const [\n          Center(child: Text('Contenuto Home')),\n          Center(child: Text('Contenuto Cerca')),\n          Center(child: Text('Contenuto Profilo')),\n        ],\n      ),\n    );\n  }\n}","Avviando l'app e ridimensionando la finestra (o cambiando dispositivo) la navigazione si adatta automaticamente: NavigationBar su mobile, NavigationRail su tablet, rail estesa su desktop.",[],1789205526634]