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

ListView: il widget per la gestione delle liste

Impariamo ad utilizzare il widget ListView per creare app mobile multipiattaforma con Flutter, e gestire al meglio le liste.
Impariamo ad utilizzare il widget ListView per creare app mobile multipiattaforma con Flutter, e gestire al meglio le liste.
Link copiato negli appunti

In questa lezione, focalizzeremo la nostra attenzione su uno degli elementi principe dello sviluppo di qualsiasi applicazione mobile che debba mostrare un elenco di elementi tra cui l’utente può scegliere: la ListView.

La classe ListView

Come visto finora, qualsiasi componente grafico in Flutter è di base un Widget, anche la ListView. Definito come uno scrolling widget, la ListView mostra un insieme di widget figli uno dopo l’altro secondo la direzione di scorrimento del listato.

Una delle peculiarità di questo widget è la possibilità di definire una ListView attraverso diverse tipologie di costruttori, lasciando così ampio respiro allo sviluppatore che può scegliere quello più congeniale alle proprie esigenze.

Vediamo quindi quali sono i costruttori a nostra disposizione e quando usarli.

Costruttore Descrizione
ListView crea un array di Widget scorrevole e lineare a partire da un oggetto List.

È opportuno utilizzare questo costruttore quando bisogna mostrare all’utente una lista contenuta di elementi, in quanto la creazione di un oggetto List richiede di processare ogni elemento che potrebbe essere visualizzato invece di processare solo gli elementi della lista che sono effettivamente visibili.

Al suo posto, è consigliabile usare il costruttore ListView.builder.

ListView.builder questo costruttore crea un array scorrevole e lineare di widget creati su richiesta.

Differentemente dal costruttore ListView, esso è più appropriato per la creazione di una ListView con un numero elevato (ma anche infinito) di elementi. Ciò è possibile in quanto vengono creati solo gli elementi visibili sullo schermo, ottimizzando i tempi di rendering.

Questo costruttore accetta in input diversi parametri, tra cui quello di maggior interesse è itemCount, che se fornito permette alla ListView di stimare la massima estensione dello scroll.

Questo costruttore, inoltre, non supporta di default il riordinamento degli elementi in un secondo momento. In questo caso è consigliabile usare i costruttori ListView o ListView.custom

ListView.custom Crea un array scorrevole e lineare di widget sulla base delle funzionalità definite dallo sviluppatore per la creazione degli elementi della lista.

Per farlo, il costruttore fa uso di un SilverChildDelegate che costruisce gli elementi. In particolare, possono essere definiti due tipi di SilverChildDelegate:

  • SilverChildListDelegate, che accetta una lista di elementi;
  • SliverChildBuilderDelegate, che fornisce gli elementi della lista tramite la callback IndexedWidgetBuilder al fine di crearli solo al momento della visualizzazione.

In particolare possiamo affermare che:

  • il costruttore ListView.builder è essenzialmente un ListView.custom con un SliverChildBuilderDelegate;
  • Il costruttore di default di ListView si comporta come un ListView.custom con un SliverChildListDelegate.
ListView.separated definisce un array scorrevole e lineare a lunghezza fissa dove gli elementi sono intervallati da una lista di elementi che fungono da separatori.

I separatori vengono visualizzati solo tra gli elementi dell’elenco, ossia il primo separatore comparirà dopo il primo elemento e l’ultimo separatore verrà mostrato prima dell’ultimo elemento.

Per creare sia gli elementi sia i separatori sono definite due callback:

  • itemBuilder che verrà invocato con indici nell’intervallo [0, itemCount)
  • separatorBuilder che verrà invocato con indici nell’intervallo [0, itemCount-1)

Questo costruttore (come il ListView.builder) è l’ideale quando si lavora con un’ampia lista di elementi, in quanto verranno costruiti solo gli elementi effettivamente visibili sullo schermo.

Analizzati i possibili costruttori, facciamo una panoramica delle principali proprietà di questo importante widget.

Proprietà Tipo Accettato Descrizione
childrenDelegate SilverChildDelegate rappresenta un delegato che fornisce la lista di elementi alla ListView. Ciò è possibile con il costruttore ListView.custom, mentre i costruttori ListView e ListView.builder ne creano uno contenente la lista di elementi e la callback IndexedWidgetBuilder
itemExtent double se definito, forza gli elementi della lista ad avere un’estensione nella direzione dello scroll. Definendo questa proprietà, il framework è in grado di ottimizzare le operazioni di rendering al cambio della posizione dello scroll
controller ScrollController definisce un oggetto ScrollController utilizzato per controllare la posizione in cui viene fatta scorrere la lista. Questo tipo di oggetto è utile per molteplici motivi, tra cui la possibilità di controllare la posizione iniziale dello scroll e quando deve essere salvata la posizione in automatico per ripristinarla successivamente.
padding EdgeInsetsGeometry definisce lo spazio in cui collocare gli elementi
physics ScrollPhysics è uno dei parametri più interessanti, in quanto definisce come la scrollView debba rispondere all’input dell’utente, come ad esempio il comportamento dello scroll dopo che l’utente smette di scorrere la lista
primary bool se impostata a false e se il numero di elementi è insufficiente a scorrere la schermata, allora l’utente non può effettuare lo scroll.

Per una visione completa di tutte le proprietà e ulteriori dettagli sui costruttori si rimanda alla documentazione ufficiale.

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à body che andremo a impostare di volta in volta con la definizione di un nuovo tipo di ListView.

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

Inizializzato il progetto, cominciamo utilizzando il costruttore più semplice messo a disposizione per noi, ossia ListView.

Creiamo quindi una nuova funzione all’interno della classe MyListView, che si chiamerà _myListView() e che conterrà semplicemente una lista di Widget di tipo ListTitle per la definizione di una singola riga della lista di altezza fissa. Grazie a questo Widget è possibile definire un testo, un sottotesto e un widget da impostare all’inizio o alla fine della riga tramite le proprietà:

  • leading, che permette di mostrare un widget prima del titolo;
  • trailing, che permette di mostrare un widget dopo il titolo.

A questo si aggiunge anche un’altra proprietà importante, che vedremo più avanti con un esempio dedicato, ossia onTap che permette di definire una GestureTapCallback per intercettare e gestire il click dell’utente sull’elemento della lista.

In questo esempio la lista di ListTitle conterrà semplicemente il titolo definito tramite la proprietà title.

class MyListViews extends StatelessWidget {
  Widget _myListView() {
    return ListView(
      children: <Widget>[
        ListTile(
          title: Text('Article number 1'),
        ),
        ListTile(
          title: Text('Article number 2'),
        ),
        ListTile(
          title: Text('Article number 3'),
        ),
        ListTile(
          title: Text('Article number 4'),
        ),
        ListTile(
          title: Text('Article number 5'),
        ),
        ListTile(
          title: Text('Article number 6'),
        ),
        ListTile(
          title: Text('Article number 7'),
        ),
        ListTile(
          title: Text('Article number 8'),
        ),
        ListTile(
          title: Text('Article number 9'),
        ),
        ListTile(
          title: Text('Article number 10'),
        ),
        ListTile(
          title: Text('Article number 11'),
        ),
        ListTile(
          title: Text('Article number 12'),
        ),
      ],
    );
  }

Invochiamo questa funzione impostandola come valore della proprietà body ed eseguiamo l’applicazione.

Figura 114. Creazione di una ListView di 12 elementi con il costruttore ListView per a) Android e b) iOS

Creazione di una ListView di 12 elementi con il costruttore ListView per a) Android e b) iOS

Come si può vedere, abbiamo ottenuto in modo semplice e veloce una lista di elementi predefiniti, ognuno di questi con il proprio titolo. Ciononostante, la lista non risulta esteticamente gradevole in quanto gli elementi sembrano essere contenuti in un’unica riga a causa della mancanza di un separatore.

Per rimediare al problema, possiamo utilizzare il metodo ListTitle.divideTiles che aggiunge un bordo di un pixel di dimensione tra ogni riga utilizzando il colore di default fornito dal tema, laddove non definito. Questo metodo restituisce un Iterable<Widget> che deve essere convertito in una lista con il metodo toList() per poter essere impostato come valore della proprietà children di ListView.

Vediamo di seguito l’esempio.

Widget _myListViewWithBasicSeparator(BuildContext context) {
    return ListView(
        children: ListTile.divideTiles(
          context: context,
          tiles: [
            ListTile(
              title: Text('Article number 1'),
            ),
            ListTile(
              title: Text('Article number 2'),
            ),
            ListTile(
              title: Text('Article number 3'),
            ),
            ListTile(
              title: Text('Article number 4'),
            ),
            ListTile(
              title: Text('Article number 5'),
            ),
            ListTile(
              title: Text('Article number 6'),
            ),
            ListTile(
              title: Text('Article number 7'),
            ),
            ListTile(
              title: Text('Article number 8'),
            ),
            ListTile(
              title: Text('Article number 9'),
            )
          ],
        ).toList());
  }

Inoltre, come è possibile notare esaminando attentamente il metodo appena scritto, abbiamo impostato per la proprietà contex di ListTitle.divideTiles con il BuildContext, un handler per la posizione di un widget nel widget tree.

Impostiamo il valore della proprietà body con il valore di ritorno fornito dal metodo appena creato e otterremo il seguente risultato una volta eseguita l’applicazione.

Figura 115. Creazione di una ListView con un separatore tramite il costruttore ListView per a) Android e b) iOS

Creazione di una ListView con un separatore tramite il costruttore ListView per a) Android e b) iOS

Il risultato ottenuto sarà esattamente uguale al precedente, con la differenza che questa volta tra gli elementi è presente un separatore grigio scuro.

Come accennato all’inizio di questa lezione, il costruttore ListView è molto pratico nel momento in cui abbiamo a che fare con una lista finita di elementi, ma per liste potenzialmente infinite occorre utilizzare un altro costruttore, ListView.builder.

Vediamolo subito definendo un nuovo metodo che si chiamerà _myListViewBuilder e che prenderà come parametro di input il BuildContex utilizzato dall’itemBuilder per creare dinamicamente ogni ListTitle, aggiungendo nel titolo l’indice corrente della ListTitle. Vediamo come.

Widget _myListViewBuilder(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context, index) {
        return ListTile(
          title: Text('Article number $index'),
        );
      },
    );
  }

In questo modo abbiamo creato una lista infinita di elementi, come si può vedere dalla figura di seguito.

Figura 116a. Creazione di una ListView infinita tramite il costruttore ListView.builder per Android

Creazione di una ListView infinita tramite il costruttore ListView.builder per Android

Figura 116b. Creazione di una ListView infinita tramite il costruttore ListView.builder per iOS

Creazione di una ListView infinita tramite il costruttore ListView.builder per iOS

In tutti i framework di sviluppo di applicazioni è spesso necessario dover definire una lista a scorrimento orizzontale, e in questo caso è necessario specificare la direzione dello scroll tramite la proprietà scrollDirection impostando l’asse a orizzontale come fatto in questo esempio.

Widget _myListViewBuilderHorizontal(BuildContext context) {
    return ConstrainedBox(
        constraints: new BoxConstraints(
          maxHeight: 50.0,
        ),
        child: ListView.builder(
          scrollDirection: Axis.horizontal,
          itemBuilder: (context, index) {
            return Container(
              margin: const EdgeInsets.symmetric(horizontal: 2, vertical: 2),
              width: 50,
              color: Colors.deepOrangeAccent,
              child: Center(child: Text('$index')),
            );
          },
        ));
  }

In questo caso, abbiamo definito la nostra lista come elemento figlio di un ConstrainedBox per evitare che la lista si espanda verticalmente.

Impostiamo il Widget di ritorno della funzione come valore della proprietà body ed eseguiamo l’applicazione per ottenere il seguente risultato.

Figura 117a. Creazione di una ListView orizzontale e infinita tramite il costruttore ListView.builder per Android

Creazione di una ListView orizzontale e infinita tramite il costruttore ListView.builder per Android

Figura 117b. Creazione di una ListView orizzontale e infinita tramite il costruttore ListView.builder per iOS

Creazione di una ListView orizzontale e infinita tramite il costruttore ListView.builder per iOS

Adesso che abbiamo preso confidenza con le ListView e il costruttore builder, rendiamo la nostra lista più carina e creiamo una lista composta da 9 elementi, ognuno dei quali con un’icona e un testo.

Creiamo quindi un nuovo metodo che si chiamerà _myListViewWithCustomIconAndCard e che prende in input il BuildContext necessario all’itemBuilder per costruire il listato.

Successivamente, definiamo due liste di elementi che conterranno le icone e i testi da mostrare associati e definiamo la nostra ListView come segue.

Widget _myListViewWithCustomIconAndCard(BuildContext context) {
    final titles = [
      'alarm',
      'pics',
      'PDF collection',
      'camera',
      'giftcard',
      'edit',
      'adb',
      'zoom in',
      'zoom out'
    ];
    final icons = [
      Icons.access_alarm,
      Icons.collections,
      Icons.picture_as_pdf,
      Icons.camera,
      Icons.card_giftcard,
      Icons.mode_edit,
      Icons.adb,
      Icons.zoom_in,
      Icons.zoom_out
    ];
    return ListView.builder(
      itemCount: titles.length,
      itemBuilder: (context, index) {
        return Card(
            child: ListTile(
              leading: Icon(icons[index], color: Colors.orange[800], size: 20),
              title: Text(titles[index]),
            ),
            elevation: 3,
            shape: StadiumBorder(
                side: BorderSide(
              color: Colors.deepOrange,
              width: 1.0,
            )));
      },
    );
  }

In particolare, nel corpo della funzione associata all’itemBuilder abbiamo definito un Card widget il cui figlio è un ListTitle con le proprietà leading e title impostate ai valori delle due liste tramite l’indice corrente. Inoltre, abbiamo personalizzato le nostre Card definendo una elevazione e una forma attraverso le proprietà elevation e shape, rispettivamente.

Impostiamo,quindi, il valore della proprietà body con il valore di ritorno fornito dal metodo appena creato e otterremo il seguente risultato.

Figura 118. Creazione di una ListView personalizzata tramite il costruttore ListView.builder per a) Android b) iOS

Creazione di una ListView personalizzata tramite il costruttore builder per a) Android b) iOS

Se agli elementi della lista volessimo aggiungere la possibilità di catturare l’evento touch che scaturisce nel momento in cui l’utente tocca uno degli elementi, basterà aggiungere la proprietà onTap definendo l’azione desiderata. In questo caso, immaginiamo di voler semplicemente stampare in console il titolo dell’elemento e modifichiamo il nostro ListTitle come nell’esempio seguente.

Widget _myListViewWithCustomIconCardAndTouchEvent(BuildContext context) {
    // . . .
        return Card(
            child: ListTile(
              leading: Icon(icons[index], color: Colors.orange[800], size: 20),
              title: Text(titles[index]),
              onTap: () {
                print(titles[index]);
              },
            ),
           // . . .
           );
     //. . .
  }

Modifichiamo quindi la proprietà body con il nuovo metodo scritto ed eseguiamo l’app.

L’applicazione è rimasta inalterata graficamente, ma ogni volta che toccheremo uno degli elementi della lista verrà stampato il relativo titolo nella console, come possiamo vedere in questo caso.

Figura 119. Output della console ad ogni click di un elemento della lista

Output della console ad ogni click di un elemento della lista

All’inizio di questa sezione ci siamo posti il problema di rendere netta la separazione tra gli elementi della lista, e per farlo abbiamo usato il metodo ListTitle.devideTiles. In realtà, come abbiamo visto all’inizio della lezione, possiamo (e dobbiamo) usare il costruttore ListView.separated che offre un maggiore controllo sulla creazione e sul contenuto del separatore.

Immaginiamo di voler creare una lista di 50 elementi composti da un titolo, sottotitolo e un’icona e intervalliamo questi elementi da un separatore contenente un testo equivalente che mostri l’indice corrente del separatore.

Per farlo, è necessario definire, oltre all’itemBuilder, anche la proprietà separatorBuilder di ListView e l’itemCount per far si che la ListView conosca la lunghezza della lista per attuare ottimizzazioni.

Creiamo quindi un nuovo metodo che si chiamerà _myListViewSeparated e che prende in input il BuildContex da passare sia al separatorBuilder che all’itemBuilder. Vediamo come.

Widget _myListViewSeparated(BuildContext context) {
    return ListView.separated(
      itemCount: 50,
      separatorBuilder: (context, int index) {
        return Container(
          child: ListTile(
              title: Text(
            'SeperatorItem $index',
            style: TextStyle(color: Colors.white),
          )),
          color: Colors.red[900],
          margin: EdgeInsets.symmetric(vertical: 10),
        );
      },
      itemBuilder: (BuildContext context, int index) {
        return Card(
          child: ListTile(
            leading: const Icon(Icons.accessibility,
                size: 40.0, color: Colors.white),
            title: Text('Title $index'),
            subtitle: Text('SubText'),
          ),
          color: Colors.amber,
        );
      },
    );
  }

Aggiorniamo quindi il valore della proprietà body ed eseguiamo l’app ottenendo quanto segue.

Figura 120. Creazione di una ListView definita tramite il costruttore ListView.separator per a) Android b) iOS

Creazione di una ListView definita tramite il costruttore ListView.separator per a) Android b) iOS

Come possiamo vedere scorrendo la lista, gli elementi sono intervallati da separatori il cui numero è pari ai itemCount-1. Questo diventa chiaro scorrendo fino alla fine della lista, dove l’ultimo elemento non è un separatore ma un item costruito dall’itemBuilder e l’ultimo indice dei separatori è pari a 48.

Ovviamente, non è necessario dover inserire del testo all’interno del separatore e possiamo costruirlo anche in modo più semplice facendo tornare al separatorBuilder un Divider, come mostrato nell’esempio seguente.

Widget _myListViewSeparatedBasic(BuildContext context) {
    return ListView.separated(
      itemCount: 50,
      separatorBuilder: (BuildContext context, int index) {
        return Divider(
          color: Colors.red[900],
          thickness: 5,
        );
      },
    // . . .
    );
  }

Lasciando quindi il resto del codice inalterato e modificando solo il separatorBuilder otterremo quanto segue.

Figura 121. Definizione di un semplice separatore per il costruttore ListView.separator

Definizione di un semplice separatore per il costruttore ListView.separator

Il codice di questa lezione è disponibile su GitHub.

Ti consigliamo anche