Nella lezione precedente, abbiamo appreso come organizzare una schermata in diverse schede per mostrare all’utente le informazioni in modo organizzato. Spesso però è necessario avere un numero di schede superiore a quattro e, secondo le indicazioni offerte dal material design, i Tabs
non sono più il pattern corretto da utilizzare. Al loro posto, è possibile sfruttare il Navigation Drawer. Questo, infatti, è perfetto quando l’app:
- deve portare l’utente a cinque o più schermate;
- ha più livelli gerarchici di navigazione;
- la navigazione è tra schermate che sono scorrelate tra loro.
In particolare, un Navigation Drawer mostra un pannello laterale che fuoriesce per mostrare i diversi punti di navigazione dell’applicazione.
Per implementare questo pattern, Flutter offre il widget Drawer
Drawer
La classe Drawer
Il Drawer
è utilizzato insieme al widget Scaffold
attraverso la proprietà drawer
.
Il pannello del Drawer
scorre orizzontalmente dal bordo dello Scaffold
che lo definisce per mostrare i livelli di navigazione dell’app e le relative strutture gerarchiche.
Prima di proseguire con un alcuni esempi pratici, vediamo le proprietà definite dal widget Drawer
.
Proprietà | Tipo Accettato | Descrizione |
---|---|---|
child
|
Widget
|
permette di definire il widget figlio che conterrà l’elenco di punti di navigazione dell’utente. In generale, il widget che viene largamente utilizzato è proprio la ListView
lezione 18
|
elevation
|
double
|
controlla la dimensione dell’ombra da visualizzare sotto il drawer. Di default il valore è impostato a 16 e non può essere negativo |
semanticLabel
|
String
|
definisce l’etichetta utilizzata dal framework di accessibilità dei contenuti per comunicare l’apertura o la chiusura del Drawer
|
In questa lezione, ci concentreremo solo sull’utilizzo della proprietà child
, lasciando al lettore come esercizio l’utilizzo delle proprietà restanti.
Esempi pratici
Entriamo nel vivo di questa lezione vedendo nel dettaglio alcuni esempi di creazione delle ListView
utilizzando alcuni dei costruttori precedentemente illustrati.
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à drawer
che andremo a impostare di volta in volta con la definizione di un nuovo esempio di Drawer
.
class MyDrawer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Lesson 19'),
),
body: Container(
child: Center(
child: Text("Default Scaffold Body"),
),
),
drawer: /*Set the desired Drawer*/,
);
}
Definito tutto l’occorrente iniziamo dalla creazione di un semplice Drawer
Widget _myBasicDrawer(BuildContext context) {
return Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text('Item 1'),
onTap: () {
print('Item 1');
},
),
ListTile(
title: Text('Item 2'),
onTap: () {
print('Item 2');
},
),
],
),
);
}
In questo semplice esempio abbiamo definito il metodo _myBasicDrawer
Drawer
child
ListView
Per questo componente, infatti, il costruttore ListView
è perfetto in quanto dovrà mostrare sempre un numero contenuto di elementi, ognuno dei quali rappresentato da un ListTitle, di cui abbiamo discusso in questa guida nella lezione 18 parlando proprio di ListView
. In particolare, la lista si compone di due ListTitle
ognuno dei quali composto:
- da un titolo;
- dalla proprietà
onTap
Impostando il metodo come valore della proprietà drawer
dello Scaffold
, avremo il risultato in figura.
Figura 130a. Esempio base di Drawer in Flutter per Android
Figura 130b. Esempio base di Drawer in Flutter per iOS
Inoltre, al tocco di ogni elemento della lista verrà stampata in console la stringa associata, come mostrato di seguito.
Figura 131. Output della stampa su console al tocco degli elementi
Nelle applicazioni che usiamo quotidianamente però, i Navigation Drawer
sono costruiti in modo da avere gli elementi suddivisi da un divisore e in modo da avere un header in cui è possibile mostrare informazioni utili all’utente o semplicemente il logo dell’applicazione.
Per questo, è necessario utilizzare due widget:
Valore | Descrizione |
---|---|
DrawerHeader
|
questo widget definisce un insieme di proprietà per la personalizzazione della regione superiore del pannello creato tramite il Drawer
child
decoration
|
Divider
|
rappresenta una sottile linea orizzontale utile per separare elementi di una ListView
thickness
|
Creiamo quindi un nuovo metodo che definisca un Drawer
composto da un DrawerHeader
e da cinque elementi ognuno separati da appositi Divider
. Sfruttiamo inoltre le proprietà leading
e trailing
per definire delle icone per ogni elemento.
Widget _myDrawerWithHeaderAndDivider(BuildContext context) {
return Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(
child: Text('Header'),
decoration: BoxDecoration(
color: Colors.deepOrange,
),
),
ListTile(
leading: Icon(Icons.mail),
title: Text('Item 1'),
onTap: () {
print('Item 1');
},
),
Divider(),
ListTile(
leading: Icon(Icons.view_agenda),
title: Text('Item 2'),
onTap: () {
print('Item 2');
},
),
Divider(),
ListTile(
leading: Icon(Icons.local_bar),
title: Text('Item 2'),
onTap: () {
print('Item 2');
},
),
ListTile(
leading: Icon(Icons.local_cafe),
title: Text('Item 3'),
onTap: () {
print('Item 3');
},
),
Divider(),
ListTile(
trailing: Icon(Icons.close),
title: Text('Close'),
onTap: () {
print('Closed!');
},
),
],
),
);
}
In questo caso, il nostro DrawerHeader
è composto da un testo e la proprietà decoration definisce un widget BoxDecoration
il cui colore è arancione scuro, come si può vedere nella figura seguente.
Figura 132. Esempio di utilizzo di DrawerHeader e Divider
Spesso però nell’header di un Drawer
abbiamo la necessità di mostrare le informazioni riguardanti l’account dell’utente, che otteniamo a seguito di un login sulla nostra app, mostrando un’immagine dell’utente, il suo nome e la sua email. Per semplificare questo passaggio e rendere più veloce lo sviluppo, Flutter offre un altro widget da utilizzare all’interno del Drawer
, ossia lo UserAccountsDrawerHeader
. Questo componente definisce un insieme di proprietà per definire le informazioni basilari dell’utente.
Creiamo quindi un nuovo Drawer
che sfrutti questo tipo di header e definisca alcuni elementi.
Widget _myDrawerWithAccountHeader(BuildContext context) {
return Drawer(
child: ListView(
children: <Widget>[
UserAccountsDrawerHeader(
accountName: Text("Antedesk"),
accountEmail: Text("myawesomeemail@example.it"),
currentAccountPicture: CircleAvatar(
backgroundColor: Colors.grey[200],
child: Icon(Icons.person, size: 60),
),
),
ListTile(
leading: Icon(Icons.mail),
title: Text('Item 1'),
onTap: () {
print('Item 1');
},
),
Divider(),
ListTile(
leading: Icon(Icons.view_agenda),
title: Text('Item 2'),
onTap: () {
print('Item 2');
},
),
Divider(),
ListTile(
leading: Icon(Icons.local_cafe),
title: Text('Item 3'),
onTap: () {
print('Item 3');
},
),
],
),
);
}
In questo caso, abbiamo definito per il widget UserAccountsDrawerHeader
accountName
accountEmail
currentAccountPicture
CircleAvatar
È importante ricordare che qualora non si definisse un colore per l’header, è opportuno aggiungere il seguente controllo sulla definizione del colore del CircleAvatar
Theme.of(context).platform == TargetPlatform.iOS ? Colors.blue : Colors.white
poichè per iOS il colore di default per l’header è bianco.
Pertanto, aggiornando la proprietà drawer
ed eseguendo l’applicazione, otterremo il seguente risultato.
Figura 133. Utilizzo di un UserAccountsDrawerHeader per a) Android b) iOS
Questo Widget offre poi la possibilità di definire ulteriori account da mostrare all’utente finale, nel caso in cui questo ne avesse più di uno. Per farlo, è necessario definire la proprietà otherAccountsPictures
che accetta una lista di Widget
da visualizzare in alto a destra nell’header. Vediamo come modificare lo UserAccountsDrawerHeader
definito prima.
// . . .
UserAccountsDrawerHeader(
accountName: Text("Antedesk"),
accountEmail: Text("myawesomeemail@example.it"),
currentAccountPicture: CircleAvatar(
backgroundColor: Colors.grey[200],
child: Icon(Icons.person, size: 60),
),
otherAccountsPictures: <Widget>[
CircleAvatar(
backgroundColor: Colors.white,
child: Text(
"A",
style: TextStyle(fontSize: 20.0),
)),
Icon(Icons.shop, size: 30, color: Colors.white)
],
),
// . . .
Come si può notare abbiamo aggiunto come widget un altro CircleAvatar
e un Icon
ottenendo il risultato mostrato di seguito.
Figura 134. Utilizzo della proprietà otherAccountsPictures di UserAccountsDrawerHeader per a) Android b) iOS
Ora che abbiamo appreso come modificare il nostro header e rendere la lista di elementi più accattivante con l’utilizzo di divisori e icone, analizziamo l’utilità del Drawer
nel poter navigare pagine non correlate dell’applicazione tramite il tocco degli elementi.
Immaginiamo quindi di aver definito un Drawer
composto da un UserAccountsDrawerHeader
e tre elementi, di cui i primi due permettono la navigazione ad una nuova pagina e il terzo serve a chiudere il Drawer
qualora l’utente non fosse intenzionato a spostarsi di pagina.
Creiamo prima le pagine che l’utente andrà a visualizzare e definiamo quindi un nuovo StatelessWidget
, che chiameremo NewPage
, il cui costruttore prende in input un titolo che viene impostato come titolo dell’AppBar
e come corpo della pagina.
class NewPage extends StatelessWidget {
final String title;
NewPage(this.title);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: Center(child: Text("I belongs to $title"))
);
}
}
Definito il widget, modifichiamo le callback
delle proprietà onTap
affinchè permettano la navigazione. La navigazione tra le pagine di un’applicazione è un argomento che tratteremo più avanti. In questa lezione ci basta sapere che per navigare tra le pagine di un’applicazione è necessario utilizzare il widget Navigator
, che organizza la navigazione secondo uno stack LIFO, e i suoi metodi push
e pop
.
Vediamo come con il seguente esempio.
Widget _myDrawerwithHeaderAndNavigation(BuildContext context) {
return Drawer(
child: ListView(
children: <Widget>[
// . . .
ListTile(
leading: Icon(Icons.local_bar),
title: Text('Item 1'),
onTap: () {
Navigator.pop(context);
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => new NewPage('Item 1')));
},
),
ListTile(
leading: Icon(Icons.local_cafe),
title: Text('Item 2'),
onTap: () {
Navigator.pop(context);
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => new NewPage('Item 2')));
},
),
Divider(),
ListTile(
trailing: Icon(Icons.close),
title: Text('Close'),
onTap: () => Navigator.pop(context),
),
],
),
);
}
Attraverso il metodo Navigator.pop
Drawer
Navigator.push
MaterialPageRoute
NewPage
Aggiornando la proprietà drawer
otterremo il seguente risultato.
Figura 135a. Esempio di navigazione tra le schermate a partire dagli elementi del Drawer per Android
Figura 135b. Esempio di navigazione tra le schermate a partire dagli elementi del Drawer per iOS
Come si può notare però, quando si naviga nell’applicazione è sempre necessario tornare alla prima schermata in cui è presente il Drawer
, poiché esso non è accessibile in tutte le schermate in cui atterra l’utente.
Per questo, va:
- creato un nuovo
Widget
StatelessWidget
Drawer
- modificata la classe
NewPage
Scaffold
drawer
drawer
Spostiamo la logica di creazione del nostro drawer
all’interno di una classe MyAlwaysAvailableDrawer
, che estende StatelessWidget
ed il cui codice è reperibile su GitHub. Successivamente, aggiungiamo la proprietà drawer
allo Scaffold
della classe NewPage
come segue.
class NewPage extends StatelessWidget {
// . .
return new Scaffold(
// . .
drawer: MyAlwaysAvailableDrawer(),
);
}
}
Aggiorniamo, quindi, la proprietà drawer
impostando come valore una nuova istanza del drawer MyAlwaysAvailableDrawer
ed eseguiamo l’applicazione per vedere il risultato finale.
Figura 136a. Definizione di un Drawer
Figura 136b. Definizione di un Drawer
Come possiamo vedere, il burger menu che nasconde il Drawer
sarà sempre presente in tutte le schermate dell’applicazione, permettendo all’utente di navigare tra esse in modo semplice e veloce.
Il codice di questa lezione è disponibile su Github.