Cyril Mottier

“It’s the little details that are vital. Little things make big things happen.” – John Wooden

Astuce #7 : Créer Des Listes à Cellules Variées

Ce dont je vais parler ci dessous peut paraitre évident pour la plupart des développeurs Android mais c'est pourtant une notion qui me parait mal connue ou mal utilisée. Le notion que je mentionne ici, c'est le type de vue des items d'une ListView.

La ListView est un composant permettant d'afficher un ensemble de données similaires sous forme de vue scrollable. C'est un widget élémentaire dans n'importe quel système d'exploitation mobile car il permet d'afficher un grand nombre de données un minimisant l'impact mémoire (seules les vues affichées à l'écran sont dans la hiérarchie de vues, système de réutilisation des vues, etc.) et en rendant la lecture par l'utilisateur la plus optimale possible (cellules souvent larges, gestures avancés comme le scroll ou le fling, etc.). Une ListView s'utilise, en général, pour afficher un nombre important de données du même type. Néanmoins, il est possible d'inclure des données un peu “exotiques” pour casser l'impression d'uniformité. Un exemple récurrent serait de de séparer les données ayant un trait commun.

Imaginons que nous ayons une longue liste de titre de films et de séries. Pour faciliter la lecture et la recherche, il peut être intéressant de trier ces vidéos par type et donc d'afficher un séparateur pour chaque section (qui peut par la même occasion casser l'impression de “bloc” d'une liste). Le code ci-dessous donne une implémentation de cette liste statique (code disponible ici) :

MainListActivity.java
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package com.cyrilmottier.android.itemviewtype;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

public class MainListActivity extends ListActivity {

    private static final int ITEM_VIEW_TYPE_VIDEO = 0;
    private static final int ITEM_VIEW_TYPE_SEPARATOR = 1;
    private static final int ITEM_VIEW_TYPE_COUNT = 2;

    private static class Video {
        public String title;
        public String description;

        public Video(String title) {
            this(title, "bla bla");
        }

        public Video(String title, String description) {
            this.title = title;
            this.description = description;
        }
    }

    private static final Object[] OBJECTS = {
            "Movies",
            new Video("Iron Man 2"),
            new Video("Adèle Blanc-Sec"),
            new Video("Twilight - Chapitre 3 : hésitation"),
            new Video("Green Zone"),
            new Video("Shrek 4, il était une fin"),
            new Video("L'Amour c'est mieux à deux"),
            new Video("Sex and the City 2"),
            new Video("Predators"),
            new Video("Inception"),
            "Series",
            new Video("Dr House (Docteur House)"),
            new Video("True Blood"),
            new Video("Smallville"),
            new Video("Sanctuary"),
            new Video("Desperate Housewives"),
            new Video("Spartacus: Blood and Sand"),
            new Video("Lost, les disparus"),
            new Video("Stargate Universe"),
            new Video("How I Met Your Mother")
    };

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

        setListAdapter(new VideoAdapter());
    }

    private class VideoAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return OBJECTS.length;
        }

        @Override
        public Object getItem(int position) {
            return OBJECTS[position];
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public int getViewTypeCount() {
            return ITEM_VIEW_TYPE_COUNT;
        }

        @Override
        public int getItemViewType(int position) {
            return (OBJECTS[position] instanceof String) ? ITEM_VIEW_TYPE_SEPARATOR : ITEM_VIEW_TYPE_VIDEO;
        }

        @Override
        public boolean isEnabled(int position) {
            // A separator cannot be clicked !
            return getItemViewType(position) != ITEM_VIEW_TYPE_SEPARATOR;
        }

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

            final int type = getItemViewType(position);

            // First, let's create a new convertView if needed. You can also
            // create a ViewHolder to speed up changes if you want ;)
            if (convertView == null) {
                convertView = LayoutInflater.from(MainListActivity.this).inflate(
                        type == ITEM_VIEW_TYPE_SEPARATOR ? R.layout.separator_list_item : R.layout.video_list_item, parent, false);
            }

            // We can now fill the list item view with the appropriate data.
            if (type == ITEM_VIEW_TYPE_SEPARATOR) {
                ((TextView) convertView).setText((String) getItem(position));
            } else {
                final Video video = (Video) getItem(position);
                ((TextView) convertView.findViewById(R.id.title)).setText(video.title);
                ((TextView) convertView.findViewById(R.id.description)).setText(video.description);
            }

            return convertView;
        }

    }
}

Les différences majeures entre la version présentée ci-dessus et une liste “classique” se situe au niveau des méthodes :

  • getViewTypeCount() : Implémentée par l'Adapter afin d'informer la ListView du nombre total de types de vues. Dans notre cas, nous avons bien 2 types de vues : les vues séparateur et les vues “vidéos”

  • getItemViewType(int position) : Retourne le type de la vue à la position position. Le type d'une vue est représenté par un entier compris entre 0 (inclus) et getItemViewCount() (exclus).

Le code de getView ressemble fortement à l'implémentation habituelle :

  • Si convertView est nulle, il faut créer la vue adaptée
  • On applique la donnée à la cellule
  • On retourne la cellule

Cette facilité d'utilisation est une conséquence directe de l'utilisation de type d'items. En effet, il n'est pas nécessaire de vérifier le type de la convertView pour savoir si cette dernière est une vue “séparateur” ou une vue “vidéos”. La ListView s'assure, elle même, du type de la convertView.

Il existe d'autres techniques permettant d'inclure des séparateurs dans vos listes comme l'inclusion d'un entête à l'ensemble des cellules (entête dont le flag de visibility est mis à View.GONE lorsque la vue n'est pas en début de section) mais celle présentée ici à l'avantage d'être très proche de l'utilisation classique des ListView. Avec cette nouvelle astuce dans votre sac, vous allez maintenant pouvoir présenter l'information aux utilisateurs de la façon la plus ergonomique et user-friendly possible !