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

Drawer per l’accesso a molteplici schermate

Impariamo a realizzare un Navigation Drawer all'interno del layour di un'app mobile multipiattaforma, sfruttando le funzionalità di Flutter.
Impariamo a realizzare un Navigation Drawer all'interno del layour di un'app mobile multipiattaforma, sfruttando le funzionalità di Flutter.
Link copiato negli appunti

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. In questa lezione vedremo quindi come implementare un Drawer e come personalizzarlo attraverso l’utilizzo di altri widget correlati.

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 di cui abbiamo parlato nella lezione 18 di questa guida.
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. Vediamo insieme come.

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, il cui scopo è quello di costruire e restituire un Drawer la cui proprietà child è valorizzata da una lista di elementi ottenuta tramite il costruttore 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, che stamperà in console il nome dell’elemento toccato.

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

Esempio base di Drawer in Flutter per Android

Figura 130b. Esempio base di Drawer in Flutter per iOS

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

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. In particolare vi sono le proprietà child e decoration per la definizione del contenuto dell’header e per l’applicazione di un background, rispettivamente
Divider rappresenta una sottile linea orizzontale utile per separare elementi di una ListView tra loro. Lo spessore di questa linea può essere modificato tramite la proprietà 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 per a) Android b) iOS

Esempio di utilizzo di DrawerHeader e Divider per a) Android b) iOS

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 le prorpietà accountName, accountEmail e currentAccountPicture per la definizione del nome utente, della sua email, e della foto profilo che viene mostrata, in questo caso all’interno di un 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

Utilizzo di un UserAccountsDrawerHeader

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

Utilizzo della proprietà otherAccountsPictures di UserAccountsDrawerHeader

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, stiamo rimuovendo dallo stack di navigazione il Drawer in modo da non trovarlo aperto quando si torna indietro nella schermata precedente, mentre con il metodo Navigator.push abbiamo inserito nello stack una nuova pagina creata attraverso la proprietà builder di MaterialPageRoute. In questo caso, la pagina visualizzata sarà il widget 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

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

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 (in questo caso uno StatelessWidget) che conterrà la definizione del Drawer;
  • modificata la classe NewPage affinché definisca nel suo Scaffold la proprietà drawer inizializzata con il drawer precedentemente creato.

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 sempre accessibile per Android

Definizione di un Drawer sempre accessibile per Android

Figura 136b. Definizione di un Drawer sempre accessibile per iOS

Definizione di un Drawer sempre accessibile per iOS

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.

Ti consigliamo anche