Costruire una UI adattiva per desktop e web in Flutter con NavigationRail

Foto di Josh Davies su Unsplash

GuideIntermedio35 min Flutter 3.x

Costruire una UI adattiva per desktop e web in Flutter con NavigationRail

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.

  1. 1

    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 {
      const NavDestinationData({
        required this.label,
        required this.icon,
        required this.selectedIcon,
      });
    
      final String label;
      final IconData icon;
      final IconData selectedIcon;
    }
    
    const destinations = <NavDestinationData>[
      NavDestinationData(
        label: 'Home',
        icon: Icons.home_outlined,
        selectedIcon: Icons.home,
      ),
      NavDestinationData(
        label: 'Cerca',
        icon: Icons.search_outlined,
        selectedIcon: Icons.search,
      ),
      NavDestinationData(
        label: 'Profilo',
        icon: Icons.person_outline,
        selectedIcon: Icons.person,
      ),
    ];

    Risultato atteso

    Una lista costante di destinazioni riutilizzabile da entrambi i widget di navigazione.

  2. 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 {
      const AdaptiveScaffold({super.key, required this.pages});
    
      final List<Widget> pages;
    
      @override
      State<AdaptiveScaffold> createState() => _AdaptiveScaffoldState();
    }
    
    class _AdaptiveScaffoldState extends State<AdaptiveScaffold> {
      int _selectedIndex = 0;
    
      void _onSelect(int index) {
        setState(() => _selectedIndex = index);
      }
    
      @override
      Widget build(BuildContext context) {
        return LayoutBuilder(
          builder: (context, constraints) {
            final isWide = constraints.maxWidth >= 600;
            final body = widget.pages[_selectedIndex];
    
            if (isWide) {
              return _buildWideLayout(body);
            }
            return _buildNarrowLayout(body);
          },
        );
      }
    }

    Risultato atteso

    Lo scaffold sa distinguere tra schermo stretto e largo, ma i due layout vanno ancora implementati.

  3. 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) {
      return Scaffold(
        appBar: AppBar(title: Text(destinations[_selectedIndex].label)),
        body: body,
        bottomNavigationBar: NavigationBar(
          selectedIndex: _selectedIndex,
          onDestinationSelected: _onSelect,
          destinations: [
            for (final d in destinations)
              NavigationDestination(
                icon: Icon(d.icon),
                selectedIcon: Icon(d.selectedIcon),
                label: d.label,
              ),
          ],
        ),
      );
    }

    Risultato atteso

    Riducendo la finestra sotto i 600px compare la barra di navigazione inferiore.

  4. 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) {
      return Scaffold(
        body: Row(
          children: [
            NavigationRail(
              selectedIndex: _selectedIndex,
              onDestinationSelected: _onSelect,
              labelType: NavigationRailLabelType.all,
              leading: const Padding(
                padding: EdgeInsets.symmetric(vertical: 16),
                child: FlutterLogo(size: 32),
              ),
              destinations: [
                for (final d in destinations)
                  NavigationRailDestination(
                    icon: Icon(d.icon),
                    selectedIcon: Icon(d.selectedIcon),
                    label: Text(d.label),
                  ),
              ],
            ),
            const VerticalDivider(thickness: 1, width: 1),
            Expanded(
              child: Scaffold(
                appBar: AppBar(title: Text(destinations[_selectedIndex].label)),
                body: body,
              ),
            ),
          ],
        ),
      );
    }

    Risultato atteso

    Allargando la finestra oltre 600px la navigazione si sposta lateralmente con la rail.

  5. 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.

    // Nel build principale, passa la larghezza:
    if (isWide) {
      return _buildWideLayout(body, extended: constraints.maxWidth >= 1000);
    }
    
    // Aggiorna la firma e la NavigationRail:
    Widget _buildWideLayout(Widget body, {required bool extended}) {
      return Scaffold(
        body: Row(
          children: [
            NavigationRail(
              extended: extended,
              selectedIndex: _selectedIndex,
              onDestinationSelected: _onSelect,
              labelType: extended ? null : NavigationRailLabelType.all,
              leading: const Padding(
                padding: EdgeInsets.symmetric(vertical: 16),
                child: FlutterLogo(size: 32),
              ),
              destinations: [
                for (final d in destinations)
                  NavigationRailDestination(
                    icon: Icon(d.icon),
                    selectedIcon: Icon(d.selectedIcon),
                    label: Text(d.label),
                  ),
              ],
            ),
            const VerticalDivider(thickness: 1, width: 1),
            Expanded(
              child: Scaffold(
                appBar: AppBar(title: Text(destinations[_selectedIndex].label)),
                body: body,
              ),
            ),
          ],
        ),
      );
    }

    Risultato atteso

    Oltre i 1000px la rail si espande mostrando le etichette accanto alle icone, come nelle app desktop moderne.

  6. 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());
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'UI Adattiva',
          theme: ThemeData(
            useMaterial3: true,
            colorSchemeSeed: Colors.indigo,
          ),
          home: AdaptiveScaffold(
            pages: const [
              Center(child: Text('Contenuto Home')),
              Center(child: Text('Contenuto Cerca')),
              Center(child: Text('Contenuto Profilo')),
            ],
          ),
        );
      }
    }

    Risultato atteso

    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.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!