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 diWidget
di tipoTab
, 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 widgetTabBar
.
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
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
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
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
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 delTab
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
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
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
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
Figura 129b. 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.