Autore Topic: [medio] ListView con layout personalizzato tramite un custom ArrayAdapter  (Letto 26425 volte)

Offline Ricky`

  • Amministratore
  • Utente storico
  • *****
  • Post: 3489
  • Respect: +506
    • Github
    • Google+
    • rciovati
    • Mostra profilo
Livello di difficoltà: medio
Versione SDK utilizzata: 4.2
Link al file compresso del progetto eclipse: file in allegato

In questo breve tutorial spiegheremo come è possibile realizzare un custom ArrayAdapter in modo da utilizzare delle custom view in una ListView.
Per coerenza ho voluto basarmi sul tutorial di Qlimax [medio] ListView con layout personalizzato tramite un SimpleAdapter - Android Developers Italia[/url] in modo da rendere ancora più ovvien le differenze tra un approcio e l'altro.
Realizzare un custom adapter è spesso la scelta obbligata se si vuole delle specifiche personalizzazioni. Anche se ad un primo sguardo può sembrare complesso in realtà il cuore del codice è sempre lo stesso e si procede poi con le dovute personalizzazioni del caso.
Bando alle ciance è ora di vedere un pochino il codice.

La classe Person e la view la associare alla riga sono praticamente le stesse di Qlimax.

Codice (Java): [Seleziona]
public class Person {
        private String name;
        private String surname;
        private int photoResource;

        public Person(String name, String surname, int photoRes) {
                this.name = name;
                this.surname = surname;
                this.photoResource = photoRes;
        }

        public String getName() {
                return name;
        }

        public String getSurname() {
                return surname;
        }

        public int getPhotoResource() {
                return photoResource;
        }
}

Codice (XML): [Seleziona]
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:padding="5dip" >

    <ImageView
       android:id="@+id/personImage"
       android:layout_width="50dip"
       android:layout_height="50dip"
       android:contentDescription="@string/person_image_content_description" >
    </ImageView>

    <TextView
       android:id="@+id/personName"
       style="@style/personNameStyle"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_toRightOf="@id/personImage" >
    </TextView>

    <TextView
       android:id="@+id/personSurname"
       style="@style/personSurnameStyle"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_alignLeft="@+id/personName"
       android:layout_alignParentBottom="true"
       android:layout_below="@id/personName" >
    </TextView>

</RelativeLayout>

Per lavorare in modo efficiente, rendendo quindi lo scroll della ListView fluido, è necessario "riciclare" gli oggetti che rappresentano ciascuna riga della nostra lista in modo da evitare di fare quello che in gergo si chiama "inflate" e di chiamare il metodo findViewById quando se non quando strettamente necessario. Questa ottimizzazione è ottenuta grazie all'uso della classe ViewHolder.
Oltre a questo concetto non c'è niente da dire poichè si tratta solo di assegnare i valori alle varie View.

Codice (Java): [Seleziona]
package it.anddev.tutorial;

import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class PersonAdapter extends ArrayAdapter<Person> {

        private int resource;
        private LayoutInflater inflater;

        public PersonAdapter(Context context, int resourceId, List<Person> objects) {
                super(context, resourceId, objects);
                resource = resourceId;
                inflater = LayoutInflater.from(context);
        }

        @Override
        public View getView(int position, View v, ViewGroup parent) {

                // Recuperiamo l'oggetti che dobbiamo inserire a questa posizione
                Person person = getItem(position);

                ViewHolder holder;

                if (v == null) {
                        v = inflater.inflate(resource, parent, false);
                        holder = new ViewHolder();
                        holder.nameTextView = (TextView) v.findViewById(R.id.personName);
                        holder.surnameTextView = (TextView) v
                                        .findViewById(R.id.personSurname);
                        holder.personImageView = (ImageView) v
                                        .findViewById(R.id.personImage);
                        v.setTag(holder);
                } else {
                        holder = (ViewHolder) v.getTag();
                }

                holder.personImageView.setImageResource(person.getPhotoResource());
                holder.nameTextView.setText(person.getName());
                holder.surnameTextView.setText(person.getSurname());

                return v;
        }

        private static class ViewHolder {
                TextView nameTextView;
                TextView surnameTextView;
                ImageView personImageView;
        }
}

L'activity principale è molto semplice e non fa altro che assegnare un instanza dell'adapter precedentemente creato all'oggetto ListView.
Il compiti di riempire l'adapter è affidato a un AsyncTask che nel nostro caso non fa altro che riempirlo con oggetti Person "a caso". Lascio a voi un utilizzo più sensato :)

Codice (Java): [Seleziona]
public class ListViewActivity extends Activity {

        ListView listView;
        PersonAdapter personAdapter;

        @Override
        public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.main);

                listView = (ListView) findViewById(R.id.personListView);

                personAdapter = new PersonAdapter(this, R.layout.row_item,
                                new ArrayList<Person>());

                listView.setAdapter(personAdapter);

                new BackgroundWorker().execute();
        }

        private class BackgroundWorker extends AsyncTask<Void, Person, Void> {

                @Override
                protected void onPreExecute() {
                        // Prima di iniziare a inserire gli elementi svuotiamo l'adapter
                        personAdapter.clear();
                        super.onPreExecute();
                }

                @Override
                protected Void doInBackground(Void... params) {

                        // Qui dentro si possono mettere le operazioni che potrebbero
                        // rallentare il caricamento della listview, come ad sempio il
                        // caricamento da db degli oggetti

                        Person[] people = {
                                        new Person("Anna", "Falchi", R.drawable.creep_1),
                                        new Person("Cameron", "Diaz", R.drawable.creep_2),
                                        new Person("Jessica", "Alba", R.drawable.creep_3),
                                        new Person("Manuela", "Arcuri", R.drawable.creep_4) };

                        // riempimento casuale della lista delle persone
                        Random r = new Random();

                        for (int i = 0; i < 200; i++) {
                                // Pubblichiamo il progresso
                                publishProgress(people[r.nextInt(people.length)]);
                        }

                        return null;
                }
               
                @Override
                protected void onProgressUpdate(Person... values) {
                        // Aggiungiamo il progresso pubblicato all'adapter
                        personAdapter.add(values[0]);
                        super.onProgressUpdate(values);
                }

        }
}

[EDIT 15/1/13]
Dopo 280 download del vecchio esempio, ho aggiornato leggermente il tutorial :-)
« Ultima modifica: 22 Gennaio 2013, 12:13:00 CET da Ricky` »

Offline Sirio22

  • Utente junior
  • **
  • Post: 93
  • Respect: +2
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S2
  • Sistema operativo:
    Windows 7/8.1
Re:[medio] ListView con layout personalizzato tramite un custom ArrayAdapter
« Risposta #1 il: 24 Ottobre 2010, 16:43:16 CEST »
0
Molto utile grazie sono riuscito a cambiare le immagini all'interno della listView dinamicamente :) thumb up!
Let Your Brain Run Away

Offline zerocool87

  • Utente junior
  • **
  • Post: 131
  • Respect: +4
    • Mostra profilo
  • Dispositivo Android:
    Htc Legend
  • Sistema operativo:
    Ubuntu 10.04
Re:[medio] ListView con layout personalizzato tramite un custom ArrayAdapter
« Risposta #2 il: 31 Ottobre 2010, 17:34:42 CET »
0
sto utilizzando questo tuo tutorial (modificato ;) ) per una chat. vorrei che i messaggi da me inviati fossero allineati a destra, mentre i messaggi da me ricevuti fossero allineati a sinistra. le sto provando tutte, ma proprio non riesco a capire come si fa...

Ho aggiunto questo nell'adapter ma va in crash l'applicazione
Codice (Java): [Seleziona]
RelativeLayout.LayoutParams params = new  RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
                               
                   
               
                if(chatMessage.getSent()==true){
               
                        Log.e("Sposto a destra",tvName.getText().toString());
                       
                       
                       
                }
                else{
                       
                         params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
                       
                }
                 convertView.setLayoutParams(params);
               
return convertView;

Offline Ricky`

  • Amministratore
  • Utente storico
  • *****
  • Post: 3489
  • Respect: +506
    • Github
    • Google+
    • rciovati
    • Mostra profilo
Re:[medio] ListView con layout personalizzato tramite un custom ArrayAdapter
« Risposta #3 il: 31 Ottobre 2010, 18:44:26 CET »
0
L'idea dovrebbe essere corretta. Cosa dice il logcat quando crasha? Prova comunque a postare il codice completo del metodo getView e il layout della riga :) Ho comunque un sospetto. Se la tua row ha come layout un RelativeLayout i parametri tipo ALIGN_PARENT_LEFT devi assegnarli ai child :)
« Ultima modifica: 31 Ottobre 2010, 18:54:24 CET da Ricky` »

Offline zerocool87

  • Utente junior
  • **
  • Post: 131
  • Respect: +4
    • Mostra profilo
  • Dispositivo Android:
    Htc Legend
  • Sistema operativo:
    Ubuntu 10.04
Re:[medio] ListView con layout personalizzato tramite un custom ArrayAdapter
« Risposta #4 il: 01 Novembre 2010, 15:41:54 CET »
0
L'idea dovrebbe essere corretta. Cosa dice il logcat quando crasha? Prova comunque a postare il codice completo del metodo getView e il layout della riga :) Ho comunque un sospetto. Se la tua row ha come layout un RelativeLayout i parametri tipo ALIGN_PARENT_LEFT devi assegnarli ai child :)


alla fine ho utilizzato una soluzione un pò "rozza" :P

Ho creato 2 layout, uno allineato a sinistra ed uno allineato a destra.  quando vado a fare l'inflate della convertView scelgo uno o l'altro :)

Offline Ricky`

  • Amministratore
  • Utente storico
  • *****
  • Post: 3489
  • Respect: +506
    • Github
    • Google+
    • rciovati
    • Mostra profilo
Re:[medio] ListView con layout personalizzato tramite un custom ArrayAdapter
« Risposta #5 il: 01 Novembre 2010, 15:49:16 CET »
0

alla fine ho utilizzato una soluzione un pò "rozza" :P

Ho creato 2 layout, uno allineato a sinistra ed uno allineato a destra.  quando vado a fare l'inflate della convertView scelgo uno o l'altro :)

E come fai a gestire il discorso del caching della converView?

Offline zerocool87

  • Utente junior
  • **
  • Post: 131
  • Respect: +4
    • Mostra profilo
  • Dispositivo Android:
    Htc Legend
  • Sistema operativo:
    Ubuntu 10.04
Re:[medio] ListView con layout personalizzato tramite un custom ArrayAdapter
« Risposta #6 il: 01 Novembre 2010, 15:52:18 CET »
0
Codice (Java): [Seleziona]
                ChatMessage chatMessage = getItem( position );

                ChatMessageViewCache viewCache;


                        if(chatMessage.getSent()==true){       
                                convertView = ( RelativeLayout ) inflater.inflate( R.layout.lista_chat_right, null );
                               
                        }
                        else{
                                convertView = ( RelativeLayout ) inflater.inflate( R.layout.lista_chat_left, null );
                               
                        }
                       
                        viewCache = new ChatMessageViewCache( convertView );
                        convertView.setTag( viewCache );

questo è il codice che uso.
se lasciavo il controllo del convertView non funzionava (non so il motivo sinceramente)

Offline Ricky`

  • Amministratore
  • Utente storico
  • *****
  • Post: 3489
  • Respect: +506
    • Github
    • Google+
    • rciovati
    • Mostra profilo
Re:[medio] ListView con layout personalizzato tramite un custom ArrayAdapter
« Risposta #7 il: 01 Novembre 2010, 15:58:57 CET »
0
Beh dal codice che hai postato semprerebbe che non effettui nessun caching... ok che scrivi nel tag ma se poi non leggi... :P

Offline zerocool87

  • Utente junior
  • **
  • Post: 131
  • Respect: +4
    • Mostra profilo
  • Dispositivo Android:
    Htc Legend
  • Sistema operativo:
    Ubuntu 10.04
Re:[medio] ListView con layout personalizzato tramite un custom ArrayAdapter
« Risposta #8 il: 01 Novembre 2010, 16:00:42 CET »
0
Beh dal codice che hai postato semprerebbe che non effettui nessun caching... ok che scrivi nel tag ma se poi non leggi... :P

non funzionava il caching con l'utilizzo di due layout differenti... ho dimenticato di togliere il tag :P

Offline Ricky`

  • Amministratore
  • Utente storico
  • *****
  • Post: 3489
  • Respect: +506
    • Github
    • Google+
    • rciovati
    • Mostra profilo
Re:[medio] ListView con layout personalizzato tramite un custom ArrayAdapter
« Risposta #9 il: 01 Novembre 2010, 16:14:53 CET »
0
non funzionava il caching con l'utilizzo di due layout differenti... ho dimenticato di togliere il tag :P

Capisco :) Io avrei lavorato per risolvere il problema e usufruire del caching (è nettamente più efficiente, se hai una lista lunga te ne accorgi scrollando), però sono scelte :)

Offline zerocool87

  • Utente junior
  • **
  • Post: 131
  • Respect: +4
    • Mostra profilo
  • Dispositivo Android:
    Htc Legend
  • Sistema operativo:
    Ubuntu 10.04
Re:[medio] ListView con layout personalizzato tramite un custom ArrayAdapter
« Risposta #10 il: 01 Novembre 2010, 16:17:07 CET »
0
Capisco :) Io avrei lavorato per risolvere il problema e usufruire del caching (è nettamente più efficiente, se hai una lista lunga te ne accorgi scrollando), però sono scelte :)


guarda io ci ho provato, ma non ha fatto che crearmi problemi il caching... inoltre è stata l'unica soluzione che mi ha funzionato. ho provato in tutti i modi a creare nuovi LayoutParams, ma andava in crash e il log non er amolto di aiuto...

Offline zerocool87

  • Utente junior
  • **
  • Post: 131
  • Respect: +4
    • Mostra profilo
  • Dispositivo Android:
    Htc Legend
  • Sistema operativo:
    Ubuntu 10.04
Re:[medio] ListView con layout personalizzato tramite un custom ArrayAdapter
« Risposta #11 il: 01 Novembre 2010, 16:21:55 CET »
0
Ho appena riprovato abilitando il caching. mi appare un unico layout.quello allineato a sinistra

Offline Ricky`

  • Amministratore
  • Utente storico
  • *****
  • Post: 3489
  • Respect: +506
    • Github
    • Google+
    • rciovati
    • Mostra profilo
Re:[medio] ListView con layout personalizzato tramite un custom ArrayAdapter
« Risposta #12 il: 01 Novembre 2010, 16:25:58 CET »
0
Ho appena riprovato abilitando il caching. mi appare un unico layout.quello allineato a sinistra

Beh è ovvio, viene fatto una sola volta l'inflate e poi non viene ripetuto più! è proprio quella l'efficienza.  :)
Comunque, se vuoi apri un thread apposito postando codice del metodo getView e del layout più il logcat che non ha senso discuterne in questo thread ;)

Offline Splact

  • Nuovo arrivato
  • *
  • Post: 26
  • Respect: +1
    • Google+
    • https://www.linkedin.com/profile/view?id=78185089
    • Splact
    • Mostra profilo
    • Splact.com
  • Dispositivo Android:
    One Plus One
  • Play Store ID:
    Dario Carella
  • Sistema operativo:
    Windows 8 64bit
Re:[medio] ListView con layout personalizzato tramite un custom ArrayAdapter
« Risposta #13 il: 13 Novembre 2010, 13:09:53 CET »
0
Ho un dubbio in merito ad un eventuale riordinamento della lista, ovvero attualmente ci riesco richiamando l'execute di BackgroundWorker:

Prima chiamata per il riempimento della listview:
Codice (Java): [Seleziona]
new BackgroundWorker("title").execute();Seconda chiamata per un riordinamento:
Codice (Java): [Seleziona]
new BackgroundWorker("min_players").execute();
E' una scelta valida ricorstruire tutta la lista in questo modo, oppure si può risolvere in un modo più "leggero" lavorando direttamente sulla lista già creata?

PS: quel parametro sta  appunto a definire l'orderby per il cursore del db usato per creare la lista
PS2: Grazie mille per il tutorial  ;-)

Offline Ricky`

  • Amministratore
  • Utente storico
  • *****
  • Post: 3489
  • Respect: +506
    • Github
    • Google+
    • rciovati
    • Mostra profilo
Re:[medio] ListView con layout personalizzato tramite un custom ArrayAdapter
« Risposta #14 il: 13 Novembre 2010, 13:49:09 CET »
0
Bella domanda :)
Innanzitutto il contenuto della tua listview proviene esclusivamente dal database ti consiglio di utilizzare un CursorAdapter in modo da evitare di andare a creare gli oggetti che compongono la tua arraylist. Magari avendo pochi oggetti non lo noti, ma la creazione dell'ArrayList a partire dal cursore può richiedere un tempo "importante".

Comunque, se proprio hai necessità di proseguire su questa strada io provere a vedere come sono le performances riordinando solo l'ArrayList con un il metodo Collections.sort, andando a definire un apposito oggetto Comparator:
http://download.oracle.com/javase/1.5.0/docs/api/java/util/Collections.html#sort(java.util.List, java.util.Comparator)

A occhio però non saprei dire se è più vantaggioso ordinare l'arraylist oppure fare la query e ricrearlo (qui bisognerebbe anche tenere conto se il campo è indice o meno).