ListView : afficher une liste d’éléments

Introduction

Vous rêvez de créer une application comme Twitter, Facebook ou Allociné ? Ouvrez ces 3 applications, vous ne remarquez rien ? Elles affichent plus d’une centaine d’éléments et permettent un scroll sans aucun ralentissement. Vous voulez connaitre leur secret hein ? et bien en lisant ce tutoriel vous aurez les clés pour créer une application  fluide et fonctionnelle.

twitter

Comment afficher une liste d’éléments ?

Premiers essais

Le premier réflexe lorsque l’on cherche à afficher une liste d’éléments, par exemple une liste de contacts est de créer une vue contenant autant de TextView que de contacts à afficher. Le code pourrait ressembler au suivant :

<ScrollView
 	android:layout_width="match_parent"
  	android:layout_height="match_parent">
 	<LinearLayout
 		android:layout_width="match_parent"
 		android:layout_height="wrap_content"
 		android:orientation="vertical">
 		<TextView
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:text="1er contact"/>	 

 		<TextView
		 	android:layout_width="match_parent"
		 	android:layout_height="wrap_content"
		 	android:text="2ème contact"/>	 	 

 		...	 	

 		<TextView
 			android:layout_height="wrap_content"
 	 		android:layout_height="wrap_content"
 			android:text="i ème contact"/>	

 		...	 	

 	</LinearLayout>
</ScrollView>

Vous vous imaginez avoir à afficher vos 200 contacts Facebook de cette façon ?
En plus le système se verrait forcé d’allouer 200 vues, ce qui ralentirai fortement l’affichage et ajouterai de la latence à notre scroll, donc promettez moi d’éviter absolument cette méthode !

La méthode propre

Heureusement Android a déjà pensé à cette éventualité, et vous propose dans son SDK une suite d’objets permettant l’affichage de liste ou de grilles d’éléments de façon simple et performant, tout en gardant le contrôle total sur l’apparence des vues que l’on souhaite afficher.

C’est pourquoi dans la liste des vues disponible apparait l’objet ListView, ça vous dit de tester ça ? Je pense que oui, sinon vous ne seriez surement pas en train de lire ce tutoriel. Créez un nouveau projet, puis ajouter une ListView dans votre fichier res/layout/activity_main.xml :

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/listView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

Votre preview devrait vous afficher l’écran suivant :
Capture d’écran 2015-02-23 à 23.22.19

 

Passons maintenant au code, nous allons commencer par afficher une liste de pseudos afin de comprendre les concepts de base de cette ListView.

Ouvrez donc le fichier MainActivity.java et récupérez un pointeur vers la ListView définir dans activity_main.xml

public class MainActivity extends ActionBarActivity {

    ListView mListView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mListView = (ListView) findViewById(R.id.listView);
    }
}

Essayons d’afficher simplement la liste suivante : “Antoine”, “Benoit”, “Cyril”, “David”, “Eloise”, “Florent”, “Gerard”, “Hugo”, “Ingrid”, “Jonathan”, “Kevin”, “Logan”, “Mathieu”, “Noemie”, “Olivia”, “Philippe”, “Quentin”, “Romain”, “Sophie”, “Tristan”, “Ulric”, “Vincent”, “Willy”, “Xavier”, “Yann”, “Zoé”
(oui je me suis amusé à tout écrire…)

Afin de remplir une ListView, il existe un objet nommé Adapter, qui va prendre en entrée une liste d’objets et un layout XML et va générer pour chaque entrée une cellule formatée. Afin de mieux comprendre, mettons nous dans le code, créez un objet ArrayAdapter comme ceci, et affecter le à votre ListView :

public class MainActivity extends ActionBarActivity {

    ListView mListView;
    String[] prenoms = new String[]{
         "Antoine", "Benoit", "Cyril", "David", "Eloise", "Florent",
         "Gerard", "Hugo", "Ingrid", "Jonathan", "Kevin", "Logan",
         "Mathieu", "Noemie", "Olivia", "Philippe", "Quentin", "Romain",
         "Sophie", "Tristan", "Ulric", "Vincent", "Willy", "Xavier",
         "Yann", "Zoé"
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

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

        //android.R.layout.simple_list_item_1 est une vue disponible de base dans le SDK android,
        //Contenant une TextView avec comme identifiant "@android:id/text1"

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(MainActivity.this,
        android.R.layout.simple_list_item_1, prenoms);
        mListView.setAdapter(adapter);
    }
}

Compilez maintenant l’application et lancez la sur votre appareil Android, le résultat devrait être le suivant :

device-2015-02-24-002036

Plutôt simple non ?

Mise en place

“C’est bien ta liste de texte, mais comment je fais pour afficher mes tweets ?” Ne soyez pas impatient, j’y arrive, désolé cela demande encore un peu de travail, mais on s’en rapproche. Il faut maintenant créer un layout pour nos cellules, pour cela clickez droit sur layout/ puis choisissez New/ressource layout file, nous appellerons notre fichier Layout “row_tweet”

Capture d’écran 2015-02-23 à 23.41.45

 

L’étape suivante est assez simple à deviner : essayer de reproduire le design d’un tweet !

Nous allons ne garder que le principal pour ce tutoriel, chez nous un tweet sera composé de :

  • un avatar (que nous réduirons à un simple carré de couleur)
  • un nom de profil (que nous appellerons pseudo)
  • un texte

Voici comment j’ai imaginé cette vue :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp"
    >

    <ImageView
        android:id="@+id/avatar"
        android:layout_width="50dp"
        android:layout_height="50dp"
        tools:background="#AAA"
        />

    <TextView
        android:id="@+id/pseudo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/black"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@+id/avatar"
        android:layout_alignTop="@+id/avatar"
        android:textStyle="bold"
        tools:text="Pseudo"
        />

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/black"
        android:layout_alignLeft="@+id/pseudo"
        android:layout_below="@+id/pseudo"
        tools:text="Texte de mon tweet"
    />

</RelativeLayout>

Ce qui donne dans la preview :Capture d’écran 2015-02-24 à 01.14.20

Avant de continuer il faut impérativement que je vous explique le comportement de la ListView, qui est assez compliqué à comprendre au début.

Afin de réduire la consommation en mémoire de notre ListView, le principe est le suivant : la ListView ne stock pas plus de vues qu’elle a la capacité d’en afficher, dès qu’une vue sors de l’écran (au scroll) elle va être réutilisée afin de produire la nouvelle vue à apparaître. On dit alors que nos vues sont recyclées.

CellReuse

 

Afin d’éviter d’appeler les méthodes findViewById à chaque réutilisation des vues, Android a rajouteé un concept, nommé ViewHolder (gardien/protecteur de vue), qui va avoir le rôle de mini controlleur, associé a chaque cellule, et qui va stocker les références vers nos sous vues (dans notre cas : titre, texte et image).

Ce contrôleur va ensuite être stocké en tant que propriété de la vue (plus précisément dans l’attribut tag) afin de pouvoir garder toujours le même principe de recyclage, une vue n’a qu’un seul ViewHolder, et inversement.

Un ViewHolder a la forme suivante :

class TweetViewHolder{
     public TextView pseudo;
     public TextView text;
     public ImageView avatar;
}

et va s’associer à la vue de cette manière :

View cellule = ...; //nous verrons plus tard comment la générer

TweetViewHolder viewHolder = (TweetViewHolder) cellule.getTag();
//comme nos vues sont réutilisées, notre cellule possède déjà un ViewHolder
if(viewHolder == null){
      //si elle n'avait pas encore de ViewHolder
      viewHolder = new TweetViewHolder();

      //récupérer nos sous vues
      viewHolder.pseudo = (TextView)  cellule.findViewById(R.id.pseudo);
      viewHolder.text   = (TextView)  cellule.findViewById(R.id.text);
      viewHolder.avatar = (ImageView) cellule.findViewById(R.id.avatar);

      //puis on sauvegarde le mini-controlleur dans la vue
      cellule.setTag(viewHolder);
}

Regardons maintenant comment nos vues sont gérées. Comme je vous ai dit, elles sont recyclées afin de réduire l’occupation mémoire, il faut donc à un moment les instancier, puis dans certains cas, réutiliser les vues déjà créées précédemment. Ce n’est pas si compliqué que ça en a l’air, regardez comment le mettre en place :

//convertView est notre vue recyclée
@Override
public View getView(int position, View convertView, ViewGroup parent) {

    //Android nous fournit un convertView null lorsqu'il nous demande de la créer
    //dans le cas contraire, cela veux dire qu'il nous fournit une vue recyclée
    if(convertView == null){
        //Nous récupérons notre row_tweet via un LayoutInflater,
        //qui va charger un layout xml dans un objet View
        convertView = LayoutInflater.from(getContext()).inflate(R.layout.row_tweet,parent, false);
    }

    TweetViewHolder viewHolder = (TweetViewHolder) convertView.getTag();
    if(viewHolder == null){
        viewHolder = new TweetViewHolder();
        viewHolder.pseudo = (TextView) convertView.findViewById(R.id.pseudo);
        viewHolder.text = (TextView) convertView.findViewById(R.id.text);
        viewHolder.avatar = (ImageView) convertView.findViewById(R.id.avatar);
        convertView.setTag(viewHolder);
    }

    //nous renvoyons notre vue à l'adapter, afin qu'il l'affiche
    //et qu'il puisse la mettre à recycler lorsqu'elle sera sortie de l'écran
    return convertView;
}

Il ne reste plus qu’à remplir notre vue cellule avec les données de nos tweet. Nous allons créer une classe contenant les données d’un tweet. Dans notre jargon d’informaticiens, nous appelons ce type de classe un objet model.

public class Tweet {
    private int color;
    private String pseudo;
    private String text;

    public Tweet(int color, String pseudo, String text) {
        this.color = color;
        this.pseudo = pseudo;
        this.text = text;
    }

    ...getters
    ...setters
}

Maintenant que nous avons cette classe, je vais vous montrer à quoi ressemble le code d’un adapter dans sa globalité :

public class TweetAdapter extends ArrayAdapter<Tweet> {

    //tweets est la liste des models à afficher
    public TweetAdapter(Context context, List<Tweet> tweets) {
        super(context, 0, tweets);
    }

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

        if(convertView == null){
            convertView = LayoutInflater.from(getContext()).inflate(R.layout.row_tweet,parent, false);
        }

        TweetViewHolder viewHolder = (TweetViewHolder) convertView.getTag();
        if(viewHolder == null){
            viewHolder = new TweetViewHolder();
            viewHolder.pseudo = (TextView) convertView.findViewById(R.id.pseudo);
            viewHolder.text = (TextView) convertView.findViewById(R.id.text);
            viewHolder.avatar = (ImageView) convertView.findViewById(R.id.avatar);
            convertView.setTag(viewHolder);
        }

        //getItem(position) va récupérer l'item [position] de la List<Tweet> tweets
        Tweet tweet = getItem(position);

        //il ne reste plus qu'à remplir notre vue
        viewHolder.pseudo.setText(tweet.getPseudo());
        viewHolder.text.setText(tweet.getText());
        viewHolder.avatar.setImageDrawable(new ColorDrawable(tweet.getColor()));

        return convertView;
    }

    private class TweetViewHolder{
        public TextView pseudo;
        public TextView text;
        public ImageView avatar;
    }
}

Il ne reste plus qu’à l’utiliser dans notre MainActivity

public class MainActivity extends ActionBarActivity {

    ListView mListView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListView = (ListView) findViewById(R.id.listView);

        List<Tweet> tweets = genererTweets();

        TweetAdapter adapter = new TweetAdapter(MainActivity.this, tweets);
        mListView.setAdapter(adapter);
    }

    private List<Tweet> genererTweets(){
        List<Tweet> tweets = new ArrayList<Tweet>();
        tweets.add(new Tweet(Color.BLACK, "Florent", "Mon premier tweet !"));
        tweets.add(new Tweet(Color.BLUE, "Kevin", "C'est ici que ça se passe !"));
        tweets.add(new Tweet(Color.GREEN, "Logan", "Que c'est beau..."));
        tweets.add(new Tweet(Color.RED, "Mathieu", "Il est quelle heure ??"));
        tweets.add(new Tweet(Color.GRAY, "Willy", "On y est presque"));
        return tweets;
    }
}

et voilà !
device-2015-02-24-210804

Le code est disponible à l’adresse suivante ListViewSample

6 commentaire sur “ListView : afficher une liste d’éléments

  1. J’ai aimé le tutoriel qui est très riche et clair.

    Cependant je veux savoir si, au lieu d’écrire les noms au niveau du code Java
    (ex : String[] prenoms = new String[]{
    “Antoine”, “Benoit”, “Cyril”, “David”, “Eloise”, “Florent”,
    “Gerard”, “Hugo”, “Ingrid”, “Jonathan”, “Kevin”, “Logan”,
    “Mathieu”, “Noemie”, “Olivia”, “Philippe”, “Quentin”, “Romain”,
    “Sophie”, “Tristan”, “Ulric”, “Vincent”, “Willy”, “Xavier”,
    “Yann”, “Zoé”
    };) on les insert dans le fichier String.xml sous forme d’un
    (ex :

    France
    Belgique
    Allemagne) comment alors les afficher avec une image à côté comme vous venez de le faire ?

    Merci de me répondre !

  2. Bonjour,

    comment ferais t’on si l’on aimerais ajouté un élément de la listview à une nos favoris, c’est a dire une autre activity qui contiendrait seulement des élément provenant de notre litview d’origine?

    Merci bien.

    1. au click sur une cellule tu peux auvegarder l’élément dans https://github.com/kevindjf/Session par exemple, puis dans ton activity “Favoris” tu récupère le contenu de cette “Session” pour l’afficher dans une liste via le même Adapter, mais en lui donnant comme entrée ta liste sauvegardée

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *