Ai fini della nostra guida ci limiteremo a visualizzare una ListView di un insieme di Prodotti recuperati da una sorgente dati in locale. Per rendere l’idea di ciò che faremo vi mostro subito uno screen dell’applicazione:
Ciò che andremo a realizzare è una applicazione che recupera un insieme di Prodotti e ne visualizza i primi 15. Successivamente faremo in modo di riconoscere l’evento di scrolling dell’utente sulla ListView e di caricare e visualizzare ulteriori prodotti solo se l’utente scende in basso nella lista dei prodotti fino a visualizzare l’ultimo disponibile.
Prima di scendere nel dettaglio vorrei mostrarvi, attraverso un diagramma delle classi, una panoramica delle classi che andremo a creare. Per semplicità ho suddiviso le classi in diversi package che nel diagramma ho contrassegnato con diversi colori:
- La classe Prodotto farà parte del package model (rosso). Essa modella l’oggetto Prodotto che sarà gestito attraverso un id e il suo nome verrà visualizzato nella nostra lista.
- La classe Controller del package controller (grigio) ha la responsabilità di orchestrare il funzionamento dell’applicazione.
- La classe MainActivity del package view (verde) ha la responsabilità di gestire la parte dell’interfaccia grafica.
- Le classi CaricamentoContenuti e ListaProdottiAdapter del package utility hanno la responsabilità di supportare le operazioni del controller. La prima classe è stata inserita per eseguire e gestire il caricamento dei prodotti, mentre la seconda classe è quella che funge da tramite tra i Dati puri e quelli da visualizzare sull’interfaccia grafica. Quest’ultima deriva dalle API di Android. Se non avete dimestichezza con gli Adapter vi consiglio di leggere una guida su Google Developer.
A questo punto vediamo di rendere più chiara l’idea di ciò che realizzeremo attraverso dei pezzi di codice. L’idea di massima è quella di avere due liste separate: listaProdotti e listaProdottiAdapter. La prima rappresenta l’intera lista dei prodotti che sarà caricata e mantenuta in memoria dalla classe Controller. La seconda rappresenta la lista dei prodotti visualizzati a video. Anche questa viene inizializzata e gestita dalla classe Controller. Nell’implementazione ho ipotizzato di caricare 50 prodotti e di volerne visualizzare soltanto 15 alla volta. Ciò vuol dire che se il quindicesimo elemento è visualizzato sulla UI allora saranno caricati altri prodotti fino ad arrivare al massimo di quelli disponibili cioè 50.
Per quanto riguarda l’interfaccia grafica qui le cose sono molto semplici. L’activity è unica e contiene una ListView nella quale si andranno a caricare i diversi prodotti rappresentati da una TextView che per semplicità viene caricata all’interno dell’Adapter. L’unica particolarità è l’attributo footerView che non è altro che uno spazio riservato nel quale andremo ad inserire il testo “Caricamento in corso…” e si andrà a posizionare nella parte bassa della lista. Quando tutti i prodotti vengono caricati questa footerView sparisce poiché nessun altro caricamento verrà effettuato.
A questo punto, il codice più interessante da visionare è quello relativo alla gestione dell’evento di Scrolling.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | listView.setOnScrollListener(new OnScrollListener(){ @Override public void onScrollStateChanged(AbsListView view, int scrollState) {} @Override public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) { int lastInScreen = firstVisibleItem + visibleItemCount; // Se tutti i prodotti sono stati caricati non visualizzare il footerView if(totalItemCount>=controller.getListaProdotti().size()){ footerView.setVisibility(View.GONE); footerView.setPadding(0, -1*footerView.getHeight(), 0, 0); } else { // La prima condizione controlla che l'ultimo Prodotto sia visualizzato // La seconda condizione controlla che non vi siano già caricamenti in corso if((lastInScreen == totalItemCount) && !(controller.isCaricamentoInCorso())){ controller.startCaricamentoContenuti(); } } } }); |
Ci basterà settare uno ScrollListener e verificare due casi possibili:
- Il primo è il caso in cui tutti i prodotti sono stati visualizzati. In tal caso sarà sufficiente nascondere il footerView.
- Il secondo caso è quello in cui bisogna procedere al caricamento dei contenuti. All’interno dell’if vi sono due condizioni. La prima è quella che l’ultimo elemento visualizzato è uguale al numero di elementi presenti nella listaProdottiAdapter ( cioè la lista dei prodotti a video). La seconda condizione, invece, è stata inserita per evitare di ricaricare più volte gli stessi prodotti poiché l’evento onScroll potrebbe essere richiamato anche più di una volta. In altre parole, nello stesso istante di tempo non accadrà mai che vi sono due caricamenti differenti degli stessi prodotti.
Il caricamento dei contenuti è stato volutamente inserito in
una classe separata per mantenere l’ordine del codice e ottimizzare il riuso
del componente. La classe, come avete già visto, è CaricamentoContenuti:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public class CaricamentoContenuti implements Runnable{ private Controller controller; private Runnable returnRes = new Runnable() { @Override public void run() { if(controller.getListaProdotti() != null && controller.getListaProdotti().size() > 0){ controller.setTitoloActivity("Lista di " + String.valueOf(controller.getListaProdottiAdapter().getCount()) + " prodotti"); controller.getListaProdottiAdapter().notifyDataSetChanged(); controller.setCaricamentoInCorso(false); } } }; @Override public void run() { controller.setCaricamentoInCorso(true); // calcolo degli id da recuperare dalla nostra sorgente dati per poi visualizzarli a video int da=controller.getListaProdottiAdapter().getCount(); int a=controller.getListaProdottiAdapter().getCount()+controller.getCountProdottiToView(); for(int j=da;j<a;j++){ if(j>controller.getListaProdotti().size()-1){ break; } controller.getListaProdottiAdapter().getListaProdottiVisualizzati().add(controller.getListaProdotti().get(j)); } controller.getMainActivity().runOnUiThread(returnRes); } public Controller getController() { return controller; } public void setController(Controller controller) { this.controller = controller; } } |
Il caricamento dei contenuti è in genere una attività che potrebbe essere bloccante per l’applicazione. Per questo motivo è bene inserire attività di questo tipo in un Thread apposito diverso dal main. Ciò spiega perché la classe implementa l’interfaccia Runnable. Il caricamento dei contenuti si trova nel metodo run().
Tuttavia, come avrete notato, la classe ha anche un attributo aggiuntivo di nome returnRes di tipo Runnable che ridefinisce un’altra attività. Una volta caricati i contenuti bisogna notificare l’adapter che i dati sono stati cambiati. In questo modo l’interfaccia si aggiornerà in maniera automatica. Questo codice è stato inserito in un oggetto Runnable aggiuntivo perché Android prevede che l’aggiornamento dei contenuti dell’interfaccia grafica sia eseguito nel main principale e non in uno secondario.
Quindi, riassumiamo quello che abbiamo detto: il caricamento dei nuovi contenuti da dare in pasto all’adapter viene eseguito in un thread secondario. Una volta finita questa attività, la responsabilità di notificare l’adapter del cambiamento avvenuto è assegnato ad un oggetto Runnable che però viene eseguito nel main Thread, grazie al metodo runOnUiThread. Insieme a ciò, ho inserito una piccola modifica del titolo dell’Activity in maniera da avere un feedback del numero dei contenuti che vengono caricati.
L’esempio che vi ho riportato è davvero banale. Tuttavia, se il recupero dei contenuti dovesse avvenire attraverso la rete con questa tecnica abbiamo un notevole risparmio di risorse per il terminale.
Per completezza, vi fornisco il codice che potete scaricare dal seguente link. Spero che questa piccola guida vi sia stata d’aiuto. Vi prego di segnalarmi eventuali errori.
0 commenti:
Posta un commento