Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial
  • Lezione 19 di 37
  • livello intermedio
Indice lezioni

Creazione di schede tramite Tabs

Lavorare con i tab è un approccio decisamente comune e molto pratico nello sviluppo di app mobile: impariamo come fare con Flutter.
Lavorare con i tab è un approccio decisamente comune e molto pratico nello sviluppo di app mobile: impariamo come fare con Flutter.
Link copiato negli appunti

Lavorare con i tab è un approccio decisamente comune e molto pratico, in quanto permette di organizzare meglio i contenuti dell'applicazione e di renderli facilmente accessibili all'utente, rispettando al contempo le linee guida offerte dal Material Design.

Flutter offre, all'interno della libreria material, tutto il necessario per organizzare una schermata in tab.

In questa lezione, vedremo come creare un'applicazione composta da tab mediante la creazione di:

  • un TabController;
  • un TabBar;
  • alcune TabBarView.

Ci soffermeremo inoltre sulla personalizzazione delle label e degli indicatori dei Tab, aspetti fondamentali per lo sviluppo della propria app.

Le classi TabController, TabBar e TabView

Per creare correttamente una schermata in tab è fondamentale comprendere appieno le potenzialità di questi widget esaminando la relazione tra questi componenti e le principali proprietà.

Come si può intuire dagli stessi nomi, il TabController è il widget che si occupa di coordinare la selezione della scheda tra un TabBar e una TabBarView, dove:

  • una TabBar si occupa di mostrare una riga orizzontale di tab rappresentata da una lista di Widget di tipo Tab, ognuno dei quali definisce sia un'icona sia un testo collocato sotto l'icona;
  • una TabBarView mostra i widget che corrispondono al tab selezionato dall'utente.

Questi tre elementi vanno definiti insieme e, nel caso in cui i componenti TabBar e TabBarView siano definiti da uno stateless widget, sarà necessario utilizzare il widget DefaultTabController che estende la classe TabController.

In questa lezione, ci focalizzeremo proprio sull'uso di questo componente.

Comprese quindi le relazioni e le dipendenze tra queste componenti, analizziamo ora le rispettive proprietà.

Iniziamo col DefaultTabController.

Proprietà Tipo Accettato Descrizione
child Widget il nodo figlio che conterrà i TabBar e TabBarView. Come vedremo negli esempi, il widget è generalmente uno Scaffold al cui interno è definita un'AppBar.
initialIndex int l'indice del tab che deve essere mostrato quando l'utente arriva alla schermata composta da Tab
length int il numero totale di tab che il controller deve gestire

In particolare, la proprietà length è di fondamentale importanza, in quanto il valore definito corrisponde al numero di Tab che andremo a definire all'interno del TabBar e di Widget per il TabBarView. Se il numero degli elementi non coincide con quello definito in length, verrà generato un errore.

Concentriamoci quindi sul widget TabBar, che definisce un insieme di proprietà molto utili, come vedremo anche negli esempi, per definire e personalizzare i tab, le label e gli indicatori. Vediamo insieme le principali proprietà che useremo.

Proprietà Tipo Accettato Descrizione
indicator Decoration definisce la forma dell'indicatore del tab
indicatorColor Color rappresenta il colore da utilizzare per l'indicatore, che di default è una linea
indicatorPadding EdgeInsetsGeometry definisce il padding orizzontale per l'indicatore
indicatorSize TabBarIndicatorSize modifica la dimensione dell'indicatore in base al TabBarIndicatorSize scelto
indicatorWeight double se definito, lo spessore dell'indicatore è modificato in base al valore impostato
isScrollable bool se impostata a true, permette di scorrere orizzontalmente i tab mostrati nel TabBar. Di default è impostata a false
labelColor Color definisce il colore che viene usato per la label della tab
labelPadding EdgeInsetsGeometry il valore impostato viene utilizzato per definire il padding per la label del tab selezionato
labelStyle TextStyle se definito, viene modificato lo stile della label del tab selezionato in accordo con il TextStyle definito
onTap ValueChanged<int> permette la definizione di una callback invocata quando l'utente clicca sul tab. Questo parametro è opzionale
tabs List<Widget> definisce la lista di Widget, tipicamente Tab, da mostrare
unselectedLabelColor Color il valore impostato viene utilizzato per definire il padding per le label dei tab non selezionati
unselectedLabelStyle TextStyle se definito, viene modificato lo stile delle label dei tab non selezionati in accordo con il TextStyle definito

Come possiamo notare, la personalizzazione dei tab è un punto chiave per la nostra applicazione e Flutter ha messo a disposizione dello sviluppatore tutto il necessario per farlo in modo semplice ed efficace. In particolare, tra le varie proprietà, c'è tabs che accetta una lista di Widget di qualsiasi natura. Ciononostante, è vivamente consigliato l'utilizzo del widget Tab che si compone di tre proprietà fondamentali:

Proprietà Tipo Accettato Descrizione
child Widget il widget da utilizzare come label del Tab
icon Widget un'icona rappresentativa della schermata da mostrare all'utente
text String il testo da mostrare all'utente e visualizzato sotto l'icona

Grazie a questo widget, è possibile definire facilmente il contenuto della proprietà tabs e modificarne l'aspetto tramite le proprietà di TabBar.

Vediamo infine le principali proprietà del TabBarView widget per la visualizzazione dei Widget associati ad ogni singolo Tab.

Proprietà Tipo Accettato Descrizione
children List<Widget> è la lista di widget, dove ognuno di questi è legato al rispettivo tab grazie alla posizione in cui si trova nell'array
physics ScrollPhysics definisce come la view deve reagire agli input degli utenti

Per ulteriori dettagli circa questi Widget e l'utilizzo dei TabController in presenza di un StatefulWidget, si rimanda alla documentazione ufficiale.

Esempi pratici

Entriamo nel vivo di questa lezione vedendo nel dettaglio alcuni esempi di creazione di tab e personalizzazioni attuabili.

Per iniziare, creiamo un nuovo progetto come mostrato nella lezione 6 di questa guida e, lasciando inalterati gli import, il metodo main() e la classe MyApp, cancelliamo il resto per aggiungere il seguente StatelessWidget, composto da uno Scaffold per la definizione dell'AppBar e la proprietà body che andremo a impostare di volta in volta con la definizione di una diversa implementazione di DefaultTabController.

class MyTabBar extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Lesson 19'),
      ),
      body: /*Set the desired Widget*/ ,
    );
  }
. . .
}

Ora che abbiamo impostato il nostro di codice di base, siamo pronti a prendere confidenza con questo importante widget. Per farlo, partiamo da un esempio di base che coinvolge tutti i componenti appena descritti.

Immaginiamo quindi di creare un DefaultTabController che:

  • avrà la proprietà length pari a 3;
  • sarà composto da uno Scaffold che conterrà a sua volta un'AppBar widget;
  • definiremo la proprietà bottom dell'AppBar con il widget TabBar.

Il TabBar conterrà, quindi, tre Tab ognuno dei quali composto da un'icona e un testo.

DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          bottom: TabBar(
            tabs: [
              Tab(icon: Icon(Icons.radio_button_unchecked), text: "tab1"),
              Tab(icon: Icon(Icons.alarm), text: "tab 2"),
              Tab(icon: Icon(Icons.alarm_add), text: "tab 3"),
            ],
          ),
  // . . .
  );

Successivamente, definiamo la proprietà body dello Scaffold creando un nuovo widget TabView e impostando la sua proprietà child ad una lista di tre Icon widget.

Vediamo quindi la soluzione completa riportata di seguito.

Widget _myTabBarBasic() {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          bottom: TabBar(
            tabs: [
              Tab(icon: Icon(Icons.radio_button_unchecked), text: "tab1"),
              Tab(icon: Icon(Icons.alarm), text: "tab 2"),
              Tab(icon: Icon(Icons.alarm_add), text: "tab 3"),
            ],
          ),
          title: Text('MyTabBar Title'),
          backgroundColor: Colors.amber,
        ),
        body: TabBarView(
          children: [
            Icon(Icons.alarm),
            Icon(Icons.directions_transit),
            Icon(Icons.directions_bike)
          ],
        ),
      ),
    );
  }

Impostando il valore della proprietà body al widget restituito dal metodo ed avviando l'applicazione, otterremo il seguente risultato.

Figura 122. Esempio di una schermata composta da Tabs per a) Android b) iOS

Esempio di una schermata composta da Tabs per a) Android b) iOS

L'implementazione di una schermata basilare composta da tab risulta perciò alquanto semplice e lascia allo sviluppatore tutto lo spazio di dedicarsi all'arricchimento del contenuto che desidera mostrare all'utente finale dell'applicazione. Infatti, il valore impostato per la proprietà children di TabBarView non necessariamente deve essere un'Icon widget o un semplice widget, ma può essere anche un Widget più articolato e creato attraverso un'opportuna classe.

Definiamo quindi, ai fini del nostro esempio, le seguenti tre classi, ognuna delle quali composta da un Container che mostra un testo collocato al centro del Container stesso.

class FirstScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: Text('Tab 1 Layout'),
      ),
    );
  }
}
class SecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: Text('Tab 2 Layout'),
      ),
    );
  }
}
class ThirdScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: Text('Tab 3 Layout'),
      ),
    );
  }
}

A questo punto, basterà sostituire il valore della proprietà children di TabBarView con un array contenente un'istanza delle tre classi appena create.

Widget _myTabBarWithScreens() {
    return DefaultTabController(
      length: 3,
      // . . .
        body: TabBarView(
          children: [FirstScreen(), SecondScreen(), ThirdScreen()],
        ),
      ),
    );
  }

Eseguiamo l'applicazione o clicchiamo sull'hot reload, ottenendo il risultato in figura.

Figura 123. Esempio di TabBarView composta da tre Widget creati tramite classi per a) Android b) iOS

Esempio di TabBarView composta da tre Widget creati tramite classi per a) Android b) iOS

Come si può notare, la struttura dei tab è rimasta inalterata, ma a cambiare è ovviamente il contenuto dei tre tab che mostreranno le scritte Tab 1 Layout, Tab 2 Layout, e Tab 3 Layout, rispettivamente dal primo al terzo Tab.

Per rendere la UI più personalizzabile possibile, si possono compiere alcune modifiche all'indicatore del tab corrente modificandone ad esempio colore, dimensione, spessore e forma.

Immaginiamo quindi di avere sempre tre Tab ognuno e di mantenere la stessa struttura per la proprietà tabs di TabBar e per la proprietà children di Scaffold, che definirà lo stesso TabBarView di prima.

Per modificare il colore, la dimensione, e lo spessore di un indicatore basterà definire le proprietà indicatorColor, indicatorWeight, e indicatorSize, rispettivamente.

Vediamo come con questo semplice esempio.

Widget _myTabBarWithIndicatorColor() {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          bottom: TabBar(
            tabs: [
              Tab(icon: Icon(Icons.radio_button_unchecked), text: "tab1"),
              Tab(icon: Icon(Icons.alarm), text: "tab 2"),
              Tab(icon: Icon(Icons.alarm_add), text: "tab 3"),
            ],
            indicatorColor: Colors.red[800],
            indicatorWeight: 10,
            indicatorSize: TabBarIndicatorSize.label,
          ),
         // . .
      ),
    );
  }

In questo caso abbiamo impostato come colore del nostro indicatore il colore rosso, uno spessore di 10 e che la dimensione dell'indicatore sia pari a quella del testo della label dell'indicatore. Aggiornando il valore della proprietà body con il nuovo metodo definito ed eseguendo l'applicazione, otterremo il seguente risultato.

Figura 124. Esempio di Tab con indicatore personalizzato per a) Android b) iOS

Esempio di Tab con indicatore personalizzato per a) Android b) iOS

Oltre a queste proprietà, possiamo definirne una quarta atta a modificare la forma dell'indicatore stesso. Per farlo, basterà impostare il valore della proprietà indicator definendo la forma desiderata. Ad esempio, supponendo di voler creare un indicatore esagonale di colore arancione scuro, imposteremo il valore della proprietà indicator come segue.

indicator: ShapeDecoration(
      shape: BeveledRectangleBorder(
          side: BorderSide(color: Colors.deepOrange[800]),
          borderRadius: BorderRadius.circular(40)),
    ),

Modifichiamo inoltre, ai fini dell'esempio, anche la proprietà indicatorSize come segue:

indicatorSize: TabBarIndicatorSize.tab,

Eseguendo l'applicazione, otterremo il risultato desiderato mostrato nella figura seguente.

Figura 125. Definizione di un indicatore esagonale per i Tab per a) Android b) iOS

Definizione di un indicatore esagonale per i Tab per a) Android b) iOS

L'indicatore del Tab non è il solo elemento personalizzabile dei tab. Tra questi vi è anche la label. In particolare, di una label è possibile definire un padding, un colore, lo stile che deve avere quando è selezionata e quando non lo è.

Ciò è possibile grazie alle proprietà:

  • labelPadding;
  • labelColor;
  • labelStyle;
  • unselectedLabelColor;
  • unselectedLabelStyle;

di cui abbiamo parlato all'inizio di questa lezione.

Oltre a queste proprietà, possiamo intercettare il tocco dell'utente su ogni singolo Tab definendo un comportamento personalizzato tramite la proprietà onTap.

Vediamo come con un semplice esempio.

Widget _myTabBarWithCustomLabelAndOnTapEvent() {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          bottom: TabBar(
            tabs: [
              Tab(icon: Icon(Icons.radio_button_unchecked), text: "tab1"),
              Tab(icon: Icon(Icons.alarm), text: "tab 2"),
              Tab(icon: Icon(Icons.alarm_add), text: "tab 3"),
            ],
            indicatorColor: Colors.red[800],
            labelColor: Colors.black54,
            labelPadding: EdgeInsets.symmetric(vertical: 10),
            labelStyle: TextStyle(fontSize: 20),
            unselectedLabelColor: Colors.blueGrey[800],
            unselectedLabelStyle: TextStyle(fontSize: 14),
            onTap: (index) {
              var content = "";
              switch (index) {
                case 0:
                  content = "tab 1";
                  break;
                case 1:
                  content = "tab 2";
                  break;
                case 2:
                  content = "tab 3";
                  break;
                default:
                  content = "Nothing selectedcapitankevin88"
                      "";
                  break;
              }
              print("Clicked on $content");
            },
          ),
          title: Text('MyTabBar Title'),
          backgroundColor: Colors.amber,
        ),
        body: TabBarView(
          children: [FirstScreen(), SecondScreen(), ThirdScreen()],
        ),
      ),
    );
  }

In questo metodo abbiamo definito:

  • un padding di 10;
  • che le label siano di colore nero quando selezionate e grigio scuro quando non lo sono;
  • che la dimensione del testo sia pari a 20 quando la label è selezionata e a 14 quando non lo è;
  • l'azione che deve essere eseguita quando un utente clicca su un Tab. In particolare, verrà stampato in console il nome del Tab selezionato dall'utente.

Aggiornando il valore della proprietà body ed eseguendo l'applicazione, avremo il seguente risultato.

Figura 126. Esempio di personalizzazione delle label dei Tab per a) Android b) iOS

Esempio di personalizzazione delle label dei Tab

Ogni qualvolta andremo a cliccare su un Tab, verrà stampato in console il nome di quest'ultimo, come mostrato di seguito.

Figura 127. Output della console quando l'utente clicca sui tab

Output della console quando l'utente clicca sui tab

Non sempre la visualizzazione dei Tab all'interno dell'AppBar è la soluzione adatta per la nostra applicazione ed è spesso necessario spostarla in basso nella schermata.

Ciò è possibile grazie alla proprietà bottomNavigationBar dello Scaffold. Vediamo insieme come.

Widget _myBottomTabBar() {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
            title: Text('MyTabBar Title'), backgroundColor: Colors.amber),
        bottomNavigationBar: Container(
            color: Colors.amber,
            child: TabBar(tabs: [
              Tab(icon: Icon(Icons.radio_button_unchecked), text: "tab1"),
              Tab(icon: Icon(Icons.alarm), text: "tab 2"),
              Tab(icon: Icon(Icons.alarm_add), text: "tab 3"),
            ])),
        body: TabBarView(
          children: [FirstScreen(), SecondScreen(), ThirdScreen()],
        ),
      ),
    );
  }

Racchiudendo all'interno di un Container il TabBar con l'elenco di Tab e impostando il Container come valore della proprietà bottomNavigationBar, siamo in grado di posizionare l'elemento in basso nella schermata, come possiamo vedere nella seguente figura.

Figura 128. Esempio di TabBar collocata in basso nella schermata per a) Android b) iOS

Esempio di TabBar collocata in basso nella schermata per a) Android b) iOS

Chiudiamo, infine, con un ultimo esempio.

Spesso, durante lo sviluppo di un'applicazione, ci si pone il problema di dover mostrare più di tre o quattro tab in una stessa schermata e per farlo si ricorrere a molte strategie, come mostrare una semplice icona o ridurre il più possibile il testo della label che la accompagna. Non sempre queste strategie sono attuabili e per risolvere questo problema Flutter offre la possibilità di scorrere i tab semplicemente impostando la proprietà isScrollable di TabBar a true.

Riportiamo di seguito un semplice esempio con otto tab, ognuno dei quali composto da un'icona, un testo, e a cui è associato un widget in TabBarView.

Widget _myTabBarWithScrollableTabs() {
    return DefaultTabController(
      length: 8,
      child: Scaffold(
        appBar: AppBar(
          bottom: TabBar(
            tabs: [
              Tab(icon: Icon(Icons.radio_button_unchecked), text: "tab1"),
              Tab(icon: Icon(Icons.alarm), text: "tab 2"),
              Tab(icon: Icon(Icons.alarm_add), text: "tab 3"),
              Tab(icon: Icon(Icons.alarm_add), text: "tab 4"),
              Tab(icon: Icon(Icons.alarm_off), text: "tab 5"),
              Tab(icon: Icon(Icons.alarm_on), text: "tab 6"),
              Tab(icon: Icon(Icons.access_alarm), text: "tab 7"),
              Tab(icon: Icon(Icons.camera), text: "tab 8"),
            ],
            isScrollable: true,
          ),
          title: Text('MyTabBar Title'),
          backgroundColor: Colors.amber,
        ),
        body: TabBarView(
          children: [
            Icon(Icons.alarm),
            Icon(Icons.directions_transit),
            Icon(Icons.directions_bike),
            FirstScreen(),
            SecondScreen(),
            ThirdScreen(),
            Icon(Icons.access_alarm),
            Icon(Icons.camera),
          ],
        ),
      ),
    );
  }

Aggiorniamo il valore della proprietà body ed eseguiamo l'app ottenendo il risultato seguente

Figura 129a. Creazione di una schermata con otto Tab scorrevoli per Android

Creazione di una schermata con otto Tab scorrevoli per Android

Figura 129b. Creazione di una schermata con otto Tab scorrevoli per iOS

Creazione di una schermata con otto Tab scorrevoli per iOS

Come si può notare, l'utente può scorrere agilmente la lista di tab e visualizzare il contenuto di interesse. Ciononostante, se in una schermata dovessero esserci così tante schede da visualizzare, forse stiamo utilizzando il pattern sbagliato e potrebbe essere consigliabile utilizzare un Drawer, come vedremo nella prossima lezione.

Il codice di questa lezione è disponibile su Github.

Ti consigliamo anche