Gestire la navigazione a schede in Flutter con TabBar e TabController
GuidePrincipiante25 min Flutter 3.x

Gestire la navigazione a schede in Flutter con TabBar e TabController

La navigazione a schede è uno dei pattern di interfaccia più diffusi nelle app mobile: consente di organizzare contenuti correlati in sezioni facilmente raggiungibili con un tap o uno swipe. Flutter offre supporto nativo tramite i widget TabBar, TabBarView e la classe TabController.

In questo tutorial vedremo come costruire un'interfaccia a schede completa: partiremo dall'approccio rapido con DefaultTabController, poi passeremo al controllo manuale con TabController per gestire la navigazione in modo programmatico, aggiungeremo schede scorrevoli e reagiremo ai cambi di scheda. Al termine avrai una base solida e riutilizzabile per qualsiasi app.

  1. 1

    Creare le schede rapidamente con DefaultTabController

    Il modo più veloce per iniziare è usare DefaultTabController, che crea e gestisce automaticamente un TabController per i widget discendenti. Basta indicare il numero di schede con length e collegare una TabBar (di solito nell'AppBar) a un TabBarView per i contenuti.

    È fondamentale che il numero di Tab nella TabBar corrisponda esattamente al numero di figli in TabBarView e al valore di length, altrimenti l'app genererà un'eccezione.

    import 'package:flutter/material.dart';
    
    class SchedeBase extends StatelessWidget {
      const SchedeBase({super.key});
    
      @override
      Widget build(BuildContext context) {
        return DefaultTabController(
          length: 3,
          child: Scaffold(
            appBar: AppBar(
              title: const Text('Le mie schede'),
              bottom: const TabBar(
                tabs: [
                  Tab(icon: Icon(Icons.home), text: 'Home'),
                  Tab(icon: Icon(Icons.star), text: 'Preferiti'),
                  Tab(icon: Icon(Icons.settings), text: 'Impostazioni'),
                ],
              ),
            ),
            body: const TabBarView(
              children: [
                Center(child: Text('Contenuto Home')),
                Center(child: Text('Contenuto Preferiti')),
                Center(child: Text('Contenuto Impostazioni')),
              ],
            ),
          ),
        );
      }
    }

    Risultato atteso

    Un'AppBar con tre schede navigabili sia con il tap sia con lo swipe orizzontale.

  2. 2

    Controllo manuale con TabController e SingleTickerProviderStateMixin

    Quando hai bisogno di controllare le schede in modo programmatico (ad esempio cambiare tab da un pulsante) o reagire ai cambiamenti, conviene creare manualmente un TabController.

    Serve uno State con SingleTickerProviderStateMixin per fornire il vsync, necessario alle animazioni. Ricorda sempre di liberare le risorse con dispose().

    class SchedeManuali extends StatefulWidget {
      const SchedeManuali({super.key});
    
      @override
      State<SchedeManuali> createState() => _SchedeManualiState();
    }
    
    class _SchedeManualiState extends State<SchedeManuali>
        with SingleTickerProviderStateMixin {
      late final TabController _tabController;
    
      @override
      void initState() {
        super.initState();
        _tabController = TabController(length: 3, vsync: this);
      }
    
      @override
      void dispose() {
        _tabController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Controller manuale'),
            bottom: TabBar(
              controller: _tabController,
              tabs: const [
                Tab(text: 'Uno'),
                Tab(text: 'Due'),
                Tab(text: 'Tre'),
              ],
            ),
          ),
          body: TabBarView(
            controller: _tabController,
            children: const [
              Center(child: Text('Pagina 1')),
              Center(child: Text('Pagina 2')),
              Center(child: Text('Pagina 3')),
            ],
          ),
        );
      }
    }

    Risultato atteso

    Le schede funzionano come prima, ma ora il controller è gestito manualmente e pronto per usi avanzati.

  3. 3

    Cambiare scheda in modo programmatico

    Avere un TabController esplicito ci permette di spostarci tra le schede da codice, ad esempio dopo il completamento di un'azione. Usa animateTo(index) per una transizione animata oppure imposta direttamente _tabController.index.

    Nell'esempio aggiungiamo un FloatingActionButton che passa alla scheda successiva in modo ciclico.

    floatingActionButton: FloatingActionButton(
      onPressed: () {
        final prossimo = (_tabController.index + 1) % _tabController.length;
        _tabController.animateTo(prossimo);
      },
      child: const Icon(Icons.arrow_forward),
    ),

    Risultato atteso

    Premendo il FAB si passa alla scheda successiva con un'animazione, tornando alla prima dopo l'ultima.

  4. 4

    Reagire ai cambi di scheda con un listener

    Spesso è utile eseguire codice quando l'utente cambia scheda (analytics, ricaricare dati, aggiornare un titolo). Aggiungi un listener al controller in initState e ricorda di rimuoverlo nel dispose.

    Un dettaglio importante: durante lo swipe il listener viene chiamato più volte. Controlla _tabController.indexIsChanging per reagire solo al cambio effettivo di scheda.

    @override
    void initState() {
      super.initState();
      _tabController = TabController(length: 3, vsync: this);
      _tabController.addListener(_onTabChanged);
    }
    
    void _onTabChanged() {
      if (!_tabController.indexIsChanging) {
        debugPrint('Scheda attiva: ${_tabController.index}');
        // Qui puoi caricare dati o aggiornare lo stato
      }
    }
    
    @override
    void dispose() {
      _tabController.removeListener(_onTabChanged);
      _tabController.dispose();
      super.dispose();
    }

    Risultato atteso

    In console viene stampato l'indice della scheda ogni volta che cambia, senza chiamate duplicate durante lo swipe.

  5. 5

    Schede scorrevoli per molti elementi

    Quando le schede sono numerose e non entrano nella larghezza dello schermo, imposta isScrollable: true sulla TabBar. In questo modo le schede potranno scorrere orizzontalmente invece di essere compresse.

    Puoi anche personalizzare l'indicatore, i colori del testo e l'allineamento con proprietà come indicatorColor, labelColor, unselectedLabelColor e tabAlignment.

    TabBar(
      controller: _tabController,
      isScrollable: true,
      tabAlignment: TabAlignment.start,
      indicatorColor: Colors.deepPurple,
      labelColor: Colors.deepPurple,
      unselectedLabelColor: Colors.grey,
      tabs: const [
        Tab(text: 'Notizie'),
        Tab(text: 'Sport'),
        Tab(text: 'Tecnologia'),
        Tab(text: 'Economia'),
        Tab(text: 'Cultura'),
        Tab(text: 'Viaggi'),
      ],
    )

    Risultato atteso

    La TabBar mostra molte schede in fila scorrevole, con testo selezionato evidenziato in viola.

  6. 6

    Best practice e mantenimento dello stato delle schede

    Per impostazione predefinita, il TabBarView mantiene in memoria i widget delle schede adiacenti, ma lo stato può andare perso se il widget viene ricostruito. Se una scheda contiene una lista con posizione di scroll o dati caricati, usa il mixin AutomaticKeepAliveClientMixin nel widget della scheda.

    Alcuni consigli finali:

    • Mantieni sempre allineati length, numero di Tab e figli di TabBarView.
    • Usa DefaultTabController per casi semplici e TabController manuale quando ti serve controllo o listener.
    • Chiama sempre dispose() sul controller creato manualmente.
    class SchedaConStato extends StatefulWidget {
      const SchedaConStato({super.key});
    
      @override
      State<SchedaConStato> createState() => _SchedaConStatoState();
    }
    
    class _SchedaConStatoState extends State<SchedaConStato>
        with AutomaticKeepAliveClientMixin {
      @override
      bool get wantKeepAlive => true;
    
      @override
      Widget build(BuildContext context) {
        super.build(context); // obbligatorio con il mixin
        return ListView.builder(
          itemCount: 50,
          itemBuilder: (context, i) => ListTile(title: Text('Elemento $i')),
        );
      }
    }

    Risultato atteso

    Cambiando scheda e tornando indietro, la posizione di scroll e lo stato della lista vengono conservati.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!