24 settembre 2011

Visualizzare un'immagine diversa per riga in una ListView Android

Dopo le vacanze, il TOEFL e il periodo esami, torno ad inserire una piccola guida, ricavata in ore e ore di ricerca sul web. Spero torni utile ad altri ;)

Recentemente ho ripreso in mano lo studio di Android e, visto che credo nell'utilità della pratica, ho pensato di creare una piccola applicazione; nulla di fantascientifico, ma abbastanza complessa da costringermi ad utilizzare le principali classi a disposizione.
Una delle opzioni che mi ha fatto più penare fino ad ora, è stata la visualizzazione di un'immagine diversa, in ogni riga di una ListView, a seconda di un determinato valore estratto da un database. In sè la cosa non dovrebbe essere particolarmente difficile, ma quando tentavo di richiedere ad Android l'ID dell'ImageView tramite cui visualizzare l'immagine, ricevevo sempre e comunque un NullPointerException.
Il codice originale che cercavo di utilizzare era qualcosa di questo tipo:
...
setContentView(R.layout.lista);
listaEventi = (ListView) findViewById(R.id.lista);
imageRow = (ImageView) findViewById(R.id.img_row);
imageRow.setImageDrawable(aDrawable);
// NullPointerException
cursor = db.query(somequery);
startManagingCursor(cursor);
adapter = new SimpleCursorAdapter(this, R.layout.row, cursor, FROM, TO);
listaEventi.setAdapter(adapter);
...
Il problema era che imageRow non veniva istanziato e quindi la successiva chiamata a setImageDrawable() generava un NullPointerException. All'inizio credevo si trattasse di un problema di ordine delle chiamate (dovevo prima settare l'adapter?), ma dopo svariate prove ho capito che il problema risiedeva proprio nell'uso del SimpleCursorAdapter: cedendo il controllo sulla generazione delle singole righe della ListView al SimpleCursorAdapter, dovevo trovare il modo di dire a quell'adapter come gestire ImageView presente all'interno di R.layout.row.
La soluzione, dopo due giorni di smanettamenti, è arrivata grazie al libro Learning Android di Marko Gargenta (a proposito, ottimo libro per principianti ^_-):
In pratica bisogna passare all'adapter un parametro di tipo ViewBinder in cui è inserita l'implementazione di setViewValue() che verrà poi utilizzata per personalizzare il comportamento dell'adapter  nella gestione delle View. Non avete capito cosa intendo? Effettivamente anch'io all'inizio ero perplessa, perciò ecco a voi un esempio concreto:

Questo è il parametro da passare all'adapter. Si, avete letto bene: è un parametro!
static final ViewBinder VIEW_BINDER = new ViewBinder(){
   public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
      if(view.getId() != R.id.img_row)
         return false;
      int type = cursor.getInt(columnIndex);
      ImageView image = (ImageView)view;
      switch(type){
      case 0:
         image.setImageResource(R.drawable.pippo);
         return true;
      case 1:
         image.setImageResource(R.drawable.pluto);
         return true;
      default:
         return true;
      }
   }
};
E questo è il codice precedente, modificato correttamente. Ora funziona :)
...
setContentView(R.layout.lista);
listaEventi = (ListView) findViewById(R.id.lista);
cursor = db.query(somequery);
startManagingCursor(cursor);
adapter = new SimpleCursorAdapter(this, R.layout.row, cursor, FROM, TO);
adapter.setViewBinder(VIEW_BINDER); // Importante
listaEventi.setAdapter(adapter);
...
Ora avete capito cosa intendevo? ^_^
Personalmente è la prima volta che mi capita di trovare una soluzione del genere e, senza l'esempio del sopraccitato libro, non credo ci sarei mai arrivata, nemmeno leggendo da cima a fondo la documentazione. Conoscete altri esempi dove viene utilizzato un parametro in maniera simile a questa, oppure è solo una chicca di Android?

1 commento: