Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

RecyclerView: dietro le quinte

Cos'è e come funziona la RecyclerView, che permette di visualizzare liste interattive di moltissimi elementi in modo efficiente.
Cos'è e come funziona la RecyclerView, che permette di visualizzare liste interattive di moltissimi elementi in modo efficiente.
Link copiato negli appunti

Nella guida allo sviluppo di GUI per Android, sono stati discussi diversi elementi grafici, come le Toolbar ed i Floating Action Button (FAB), che compongono ormai da anni le interfacce delle più moderne applicazioni Android.

Tra tutti, il componente fondamentale della quasi totalità delle applicazioni esistenti è il RecyclerView, introdotto con un esempio pratico in questa lezione.

Questo componente ha offerto fin dalle sue prime implementazioni (risalenti al 2014, con il rilascio di Android Lollipop) un nuovo approccio nella risoluzione di un problema comune: la creazione di liste per la visualizzazione di dati ottenuti da un servizio remoto o da un database locale.

In questo approfondimento, vedremo più nel dettaglio i suoi componenti per capirne a pieno il funzionamento.

Perché RecyclerView

È ormai un'usanza comune presentare all'utente una lista di elementi contenente un sottoinsieme di informazioni che la caratterizzano (come foto, un titolo e un sottotitolo) per aiutarlo nella scelta di quale dettaglio consultare. Dal punto di vista dello sviluppo, la realizzazione di questi elementi si riporta alla definizione di un insieme di View da popolare con le informazioni da mostrare nella viewport, ossia la porzione di schermo visibile dall'utente.

Nonostante venga visualizzato solo un sottoinsieme di elementi sullo schermo, in realtà l'applicazione deve creare il layout anche per tutti gli elementi che non sono visibili nella schermata, senza contare che ogni elemento visualizzato deve essere salvato in memoria per poterlo mostrare in un secondo momento all'utente senza doverlo ricreare.

Questo meccanismo è trascurabile nel caso in cui si debba lavorare con liste di piccole dimensioni. In caso contrario, però, diventa impraticabile utilizzare questo approccio. Purtroppo, il caricamento di un grande insieme di elementi in una sola volta porta ad una rapida saturazione delle memoria, con impatti spiacevoli sulla user experience e sulle prestazioni dell'applicazione.

È qui che viene in aiuto il componente RecyclerView.

Anziché creare tutti gli elementi della lista durante lo scroll, esso mantiene in una coda, chiamata recycler bin (cestino per il riciclaggio), alcuni degli elementi precedentemente visualizzati per riutilizzarli in un secondo momento. Infatti, durante lo scorrimento della lista, il RecyclerView recupererà dalla coda un elemento e lo popolerà con le nuove informazioni da mostrare all'utente.

Figura 1. Esempio di riutilizzo (click per ingrandire)

Esempio di riutilizzo

Proprio questo meccanismo di riciclo e i vantaggi offerti (riportati nella tabella seguente) hanno fatto sì che il RecyclerView si affermasse in questi anni come soluzione principe nella creazione delle liste, sostituendo completamente il suo predecessore: la ListView.

Pro Riciclo delle viste attraverso l'utilizzo del pattern ViewHolder
Supporto di liste orizzontali, verticali, grid classiche e sfalsate (staggered grid)
Supporto per lo scroll orizzontale e verticale (non disponibile con il ListView)
Miglioramenti in termini di performance e di occupazione della memoria in quanto non è necessario creare il layout da popolare con le informazioni del data source per via del riutilizzo
Integrazione di animazioni per aggiungere, aggiornare e rimuovere oggetti
Contro Aumento della complessità
Mancanza di un metodo nativo per intercettare il click su un elemento della lista

Componenti

Uno degli aspetti più interessanti del RecyclerView è proprio la sua modularità, che lo rende facilmente modificabile e riutilizzabile in diversi punti dell'applicazione.

Nella seguente figura, è riportato un semplice schema di comunicazione tra i componenti coinvolti nella creazione di una lista tramite il RecyclerView.

Figura 2. Comunicazione tra i componenti durante la creazione di una lista tramite RecyclerView (click per ingrandire)

Comunicazione tra i componenti durante la creazione di una lista tramite RecyclerView

Vediamo nel dettaglio ciascuno di tali componenti.

Componente Tipologia Descrizione
Adapter RecyclerView.Adapter È responsabile di estrarre i dati dal Data Source e di usare questi dati per creare e popolare i ViewHolder. Quest’ultimi saranno poi inviati al Layout Manager del RecyclerView.Adapter.
ViewHolder RecyclerView.ViewHolder È la chiave di volta tra il RecyclerView e l'Adapter e permette la riduzione nel numero di view da creare. Questo oggetto infatti fornisce il layout da popolare con i dati presenti nel DataSource e viene riutilizzato dal RecyclerView per ridurre il numero di layout da creare per popolare la lista.
Layout Manager RecyclerView.LayoutManager È responsabile della creazione e del posizionamento delle view all'interno del RecyclerView. Esistono diverse tipologie di LayoutManager come il LinearLayoutManager utilizzato per creare liste orizzontali o verticali.
DataSource List È l'insieme di dati utilizzato per popolare la lista tramite l'Adapter

Meccanismo di riciclo: esempio pratico

Creiamo un nuovo progetto Android, come illustrato in questa lezione, e chiamiamolo RecyclerViewHMTLit.

Nell'app gradle aggiungiamo alcune librerie, che ci permetteranno di gestire:

  • il RecyclerView;
  • il caricamento delle immagini tramite la libreria Picasso, di cui si è parlato in questa lezione;
  • il binding tra layout XML e componenti con ButterKnife, introdotto invece in questa lezione.

dependencies {
	// ...
	implementation 'com.android.support:design:27.1.1'
	implementation 'com.android.support:recyclerview-v7:27.1.1'
	implementation 'com.squareup.picasso:picasso:2.71828'
	implementation 'com.jakewharton:butterknife:8.8.1'
	annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}

Dopo aver configurato correttamente tutte le librerie a nostra disposizione, siamo pronti per creare la nostra lista con RecyclerView e a comprenderne il meccanismo di riciclo dei ViewHolder con un semplice esempio basato sul colore del nostro ViewHolder.

Per fare ciò i passi da compiere sono i seguenti:

  • creazione di un asset di colori basati su un gradiente e di una classe che recuperi il colore in base al contesto e al ViewHolder creato;
  • modifica del file activity_main.xml per aggiungere il RecyclerView e creazione di un apposito layout per il ViewHolder;
  • implementazione dell'Adapter e del ViewHolder;
  • definizione dell'interfaccia per il click sull'oggetto;
  • implementazione del RecyclerView nella MainActivity.

Definizione dei colori di background

All'interno del nostro package principale definiamo un nuovo package di nome utils e creiamo la classe ColorUtils definita qui.

Questa classe definisce al suo interno il metodo statico seguente:

public static int getViewHolderBackgroundColorFromInstance(Context context, int instanceNum){..}

Esso, dato il contesto dell'applicazione e un intero che rappresenta l'ordine in cui il ViewHolder è stato creato, ritorna un intero che rappresenta il colore da utilizzare come background.

Nonostante la sua semplicità, questo metodo è fondamentale per capire come i ViewHolder vengano riciclati all'interno di un RecyclerView.

Non resta che definire l'asset di colori. In questo caso, definiamo all'interno del file colors.xml (consultabile a questo link) un gradiente di colori che va dal bianco all'arancione passando per il giallo.

Modifica e creazione dei layout

Apriamo il layout della MainActivity, activity_main.xml, e sostituiamo la TextView di default con il componente RecyclerView come segue.

...
	<android.support.v7.widget.RecyclerView
		android:id="@+id/rv_colored"
		android:layout_width="match_parent"
		android:layout_height="match_parent"/>
	...

Creiamo quindi un nuovo layout, che verrà utilizzato dal ViewHolder e popolato a run-time dall'Adapter. Chiamiamo questo layout list_item e definiamo al suo interno un FrameLayout contenente:

  • una ImageView che mostrerà il logo di HTML.it
  • una TextView che mostrerà un numero corrispondente alla reale posizione dell'elemento nella lista;
  • una TextView contenente l'effettivo indice del ViewHolder che ci permetterà di capire come vengono ri-utilizzati i ViewHolder creati.

Per completezza, si riporta al seguente link l'implementazione del layout list_item.

Implementazione dell'Adapter e del ViewHolder

Creiamo ora un nuovo package che conterrà al suo interno l'Adapter ed il relativo ViewHolder. Chiamiamo il nuovo package Adapter, ed al suo interno creiamo una nuova classe ItemAdapter.

Per poter implementare appieno la classe ItemAdapter è necessario creare prima la classe ItemViewHolder come inner class.

l'implementazione della classe ItemViewHolder è alquanto semplice. Tramite ButterKnife facciamo il binding tra gli elementi del layout list_item creato in precedenza e i relativi oggetti View che li caratterizzano in Java. Successivamente, definiamo il costruttore della classe in cui effettuiamo il binding tramite ButterKnife e implementiamo il metodo bind che ha il compito di impostare il testo rappresentante la posizione dell'elemento corrente.

public class ItemAdapter {
	// ...
	class ItemViewHolder extends RecyclerView.ViewHolder{
		@BindView(R.id.tv_item_number)
		TextView mListItemNumberTV;
		@BindView(R.id.tv_view_holder_index)
		TextView mVHIndexTV;
		@BindView(R.id.iv_logo)
		ImageView mIVLogp;
		public ItemViewHolder(View itemView) {
			super(itemView);
			ButterKnife.bind(this, itemView);
		}
		void bind(int listIndex) {
			mListItemNumberTV.setText(String.valueOf(listIndex));
		}
	}
}

Ora possiamo finalmente estendere la classe RecyclerView.Adapter come segue.

public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ItemViewHolder> {
	private static int viewHolderCount;
	private int mNumberItems;
	private Context parentContex;
	public ItemAdapter(int numberOfItems) {
		mNumberItems = numberOfItems;
		viewHolderCount = 0;
	}
//...
}

In particolare, abbiamo definito tre attributi che corrispondono al numero di ViewHolder creati, al numero di elementi della lista totali e al Context del parent, e abbiamo definito il costruttore della classe.

Per mostrare il meccanismo di riciclo, però, dobbiamo implementare i metodi onCreateViewHolder e onBindViewHolder propri della classe RecyclerView.Adapter.

In particolare, nel metodo onCreateViewHolder andiamo ad abilitare la creazione di un nuovo ItemViewHolder, che come parametro prende in ingresso un oggetto di tipo View, ossia il layout list_item. Creato l'oggetto, impostiamo il colore di background invocando il metodo getViewHolderBackgroundColorFromInstance della classe ColorUtils sulla base del valore corrente del viewHolderCount. Infine, impostiamo l'informazione su quale ViewHolder stiamo visualizzando e incrementiamo di un’unità la proprietà viewHolderCount.

@Override
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
	parentContex = parent.getContext();
	int layoutIdForListItem = R.layout.list_item;
	LayoutInflater inflater = LayoutInflater.from(parentContex);
	View view = inflater.inflate(layoutIdForListItem, parent, false);
	ItemViewHolder holder = new ItemViewHolder(view);
	int backgroundColorForViewHolder = ColorUtils
			.getViewHolderBackgroundColorFromInstance(parentContex, viewHolderCount);
	holder.mVHIndexTV.setText("ViewHolder index: " + viewHolderCount);
	holder.itemView.setBackgroundColor(backgroundColorForViewHolder);
	viewHolderCount++;
	return holder;
}

Nel metodo onBindViewHolder, invece, andiamo a compiere solo due semplici operazioni:

  • l'invocazione del metodo bind della classe ItemViewHolder per impostare la posizione corrente dell'elemento;
  • il caricamento dell'immagine del logo di HTML.it dalla cartella drawable.

@Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
	holder.bind(position);
	Picasso.get()
			.load(R.drawable.logo_open)
			.placeholder(R.mipmap.ic_launcher)
			.error(R.mipmap.ic_launcher)
			.into(holder.mIVLogp);
}

Modifica della MainActivity

Spostiamoci adesso sulla MainActivity per implementare la RecyclerView e il metodo di reset della lista.

Definiamo tre variabili per rappresentare il numero di elementi nella lista, il RecyclerView e l'Adapter per quest’ultimo. Inoltre, aggiungiamo la notazione @BindView per il RecyclerView in modo da mapparlo successivamente nel metodo onCreate.

private static final int NUM_LIST_ITEMS = 100;
private ItemAdapter mAdapter;
@BindView(R.id.rv_colored)
RecyclerView mList;

Nel metodo onCreate, eseguiamo i seguenti passi:

  • effettuiamo il binding tramite ButterKnife;
  • creiamo un nuovo LayoutManager di tipo LinearLayoutManager e lo associamo al RecyclerView;
  • creiamo un nuovo adapter di tipo ItemAdapter a cui passiamo il numero di elementi da creare e lo associamo al RecyclerView.

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
	ButterKnife.bind(this);
	LinearLayoutManager layoutManager = new LinearLayoutManager(this);
	mList.setLayoutManager(layoutManager);
	mList.setHasFixedSize(true);
	mAdapter = new ItemAdapter(NUM_LIST_ITEMS);
	mList.setAdapter(mAdapter);
}

Gestione del clic sull'oggetto

Il RecyclerView differentemente dal ListView non offre un metodo nativo per intercettare il click su un elemento della lista. Per aggiungere tale comportamento basta compiere pochi semplici passi. Vediamoli insieme.

Sempre all'interno della classe ItemAdapter, creiamo l'interfaccia ItemClickListener che riceve il messaggio di clic e definiamo una variabile statica per il listener:

final private ItemClickListener mOnClickListener;
public interface ItemClickListener {
	void onListItemClick(int clickedItemIndex);
}

Modifichiamo il costruttore della classe ItemAdapter per passare il listener e salvarlo nella variabile mOnClickListener:

public ItemAdapter(int numberOfItems, ItemClickListener listener) {
	// . . .
	mOnClickListener = listener;
}

Implementiamo la classe ItemViewHolder con l'interfaccia View.OnClickListener per implementare il metodo onClick che verrà invocato ogni qualvolta l'utente cliccherà su un elemento della lista.

class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
	//...
		public ItemViewHolder(View itemView) {
			// ...
			itemView.setOnClickListener(this);
		}
	@Override
	public void onClick(View view) {
		int clickedPosition = getAdapterPosition();
		mOnClickListener.onListItemClick(clickedPosition);
	}
}

Infine, modifichiamo come segue la MainActivity per implementare l'interfaccia ItemClickListener e modificare la creazione dell'ItemAdapter.

public class MainActivity extends AppCompatActivity implements ItemAdapter.ItemClickListener {
	// ...
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// ... 
		mAdapter = new ItemAdapter(NUM_LIST_ITEMS, this);
		mNumbersList.setAdapter(mAdapter);
	}
	@Override
	public void onListItemClick(int clickedItemIndex) {
		if (mToast != null) {
			mToast.cancel();
		}
		String toastMessage = "Item #" + clickedItemIndex + " clicked.";
		mToast = Toast.makeText(this, toastMessage, Toast.LENGTH_LONG);
		mToast.show();
	}
}

Risultato finale

Eseguiamo infine la nostra applicazione per vedere effettivamente il risultato del lavoro fatto.

Figura 3. Esecuzione dell'applicazione (click per ingrandire)

Esecuzione dell'applicazione

Come è possibile vedere, l'applicazione ci mostra una lista di elementi composti da:

  • un numero che identifica la posizione dell'elemento che stiamo visualizzando;
  • il logo di HMTL.it caricato attraverso la libreria Picasso;
  • l'indice del ViewHolder che è stato creato e visualizzato.

In questo caso, scorrendo la lista si può notare che il numero relativo della posizione aumenta e il colore del background cambia finché non si raggiunge il ViewHolder avente come indice il valore 12.

Dall'elemento 13 della lista in poi, il RecyclerView mette in moto il meccanismo di riciclo dei ViewHolder, recuperandoli dal recycle bin per popolarli con le nuove informazioni fornite dall'Adapter.

Si potrebbe (erroneamente) pensare che il RecyclerView ritulizzi in modo ordinato i 13 ViewHolder creati, ma in realtà non è così.

Come mostrato nella Figura 2, scorrendo velocemente la lista e poi osservandola, si può notare l'efficienza del meccanismo di riciclo che recupera dal suo recycler bin i ViewHolder in quel momento disponibili e li mostra all'utente finale senza perdite in termini di prestazioni.

Questo ovviamente si traduce in una lista in cui le informazioni restano sempre le stesse, ma ciò che cambia è l'ordine in cui i ViewHolder sono utilizzati dal RecyclerView.

Infine, cliccando su uno degli elementi comparirà un Toast che ci comunicherà quale elemento è stato cliccato.

Figura 4. Esecuzione dell'evento clic su un elemento della lista (click per ingrandire)

Esecuzione dell'evento clic su un elemento della lista

Questo meccanismo ha fatto sì che RecyclerView diventasse lo strumento principale per la realizzazione di liste, nonostante la sua implementazione non sia così rapida come quella del ListView di cui si è parlato in precedenza qui.

Il codice di questa lezione è disponibile su GitHub.


Ti consigliamo anche