ListView contenant des vues personnalisées

Les ListView d’Android ne permettent pas d’afficher que de simples éléments. Il est tout à fait possible de définir nos propres vues à afficher à chaque ligne de la liste. C’est l’objet de ce second tutoriel.

But de ce tutoriel :

  • Afficher sur une ligne le titre et le contenu d’une « news »
  • Afficher l’url du site quand on clique sur un élément de la liste
  • Confectionner son propre adapteur pour afficher sa vue personnalisée

On entre déjà dans un tutoriel un peu plus complexe, nous allons aborder un point très important dans la programmation sous Android. Puisque les ListView sont des « layouts » les plus utilisés, il est indispensable de pouvoir les personnaliser comme bon nous semble.

Dans un premier temps, nous allons créer la vue de notre news. Pour ce faire, déroulez le menu File > New > Android XML File et remplissez la fenêtre de la façon suivante :

Nous avons créé un « layout » que nous pouvons éditer dans res > layout > news_vue.xml. Le but de ce deuxième tutoriel est d’afficher le titre et le contenu de la news. Nous allons donc rajouter 2 « TextView » comme ci-dessous :

<?xml version="1.0" encoding="utf-8">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <TextView
        android:id="@+id/tvTitreNews"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Titre de la news" />
    <TextView
        android:id="@+id/tvContenuNews"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Contenu de la news" />
</LinearLayout>

Comment faire maintenant pour intégrer ce « layout » à chacune de nos lignes de notre ListView ? Nous rentrons dans quelque chose d’un peu plus compliqué. Pour y parvenir, il va falloir créer 3 nouvelles classes : News, VueNews et AdapterListeNews.

La News n’est qu’une classe qui va contenir notre news, qui va la représenter. Nous voulons que notre news sauvegarde son id, son titre, son contenu et son url :

/**
* Tutoriel n°2 dans le cadre du blog Randoomz<br />
* Classe du domaine pour représenter une news
*
* @author Gerard
*/
public class News {
    private int id;
    private String titre;
    private String contenu;
    private String url;

    public News(int id, String titre, String contenu, String url) {
        super();
        this.id = id;
        this.titre = titre;
        this.contenu = contenu;
        this.url = url;
    }
    public int getId() {
        return id;
    }
    public String getTitre() {
        return titre;
    }
    public String getContenu() {
        return contenu;
    }
    public String getUrl() {
        return url;
    }
}

La VueNews est la classe qui va utiliser notre « layout » news_vue.xml afin de la mettre en page dans notre ListView.

Nous devons l’étendre à l’élément racine de notre fichier xml, récupérer les « TextView » pour les remplir à partir d’une news qu’on récupère :

/**
* Tutoriel n°2 dans le cadre du blog Randoomz<br />
* Vue qui va utiliser notre layout news_vue.xml afin de la mettre en page dans
* notre ListView
*
* @author Gerard
*/
public class VueNews extends LinearLayout {
    private TextView tvTitre;
    private TextView tvContenu;
    private News news;

    public VueNews(Context context, News news) {
        super(context);

        initLayout(context);
        initComposants();
        setNews(news);
    }
    // suite plus bas ...
}

Il nous reste à initialiser les différents éléments de la classe.

L’initialisation du « layout » doit se faire par déserialisation du fichier xml. Nous ne pouvons utiliser la méthode setContentView, il faut donc utiliser cette alternative :

private void initLayout(Context context) {
    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Conext.LAYOUT_INFLATER_SERVICE);
    inflater.inflate(R.layout.news_vue, this, true);
}

L’initialisation des composants de la vue sont maintenant récupérable par la méthode findViewById :

private void initComposants() {
    this.tvTitre = (TextView) this.findViewById(R.id.tvTitreNews);
    this.tvContenu = (TextView) this.findViewById(R.id.tvContenuNews);
}

L’initialisation du contenu de ses composants doivent se faire dans le setter de la news puisque, lorsqu’on change de news, on doit pouvoir modifier les composants de la vue :

private void initContenuComposants(News news) {
    this.tvTitre.setText(news.getTitre());
    this.tvContenu.setText(news.getContenu());
}

public News getNews() {
    return news;
}

public void setNews(News news) {
    this.news = news;
    initContenuComposants(news);
}

Ceci est une bonne chose de faite. Nous avons déjà la classe qui va contenir notre news et la vue pour chaque ligne de notre ListView. Il nous reste une chose à faire avant de modifier la classe principale pour parvenir à intégrer nos nouvelles classes, programmer notre adapter pour nos vues.

L’utilité de coder son adapter est de redéfinir l’une de ses méthodes (getView) pour renvoyer notre vue personnalisée (VueNews). Pour commencer, nous devons récupérer le contexte de l’application, la liste des données (les news) et l’étendre avec la classe BaseAdapter :

/**
* Tutoriel n°2 dans le cadre du blog Randoomz<br />
* Adapter de la ListView de nos news
*
* @author Gerard
*/
public class AdapterListeNews extends BaseAdapter {
    private Context context;
    private List<News> liste;

public AdapterListeNews(Context context, List<News> liste) {
        this.context = context;
        this.liste = liste;
    }
    // suite plus bas ...
}

Ceci étant fait, eclipse vous oblige à devoir implémenter plusieurs méthodes. Générer les et compléter les de la façon suivante :

@Override
public int getCount() {
    return this.liste.size();
}

@Override
public Object getItem(int pos) {
    return this.liste.get(pos);
}

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

@Override
public View getView(int pos, View vue, ViewGroup parent) {
    return vue;
}

Vous vous en douter, notre dernière méthode prend tout son sens si on ne renvoi pas la vue qu’il attend mais notre propre vue :

@Override
public View getView(int pos, View vue, ViewGroup parent) {
    VueNews vueNews = (VueNews) vue;
    // La vue n'existe pas encore, on l'a construit
    if (vue == null)
        vueNews = new VueNews(this.context, (News) getItem(pos));
    // La vue existe déjà, on l'a reconstruit
    else
        vueNews.setNews((News) getItem(pos));
    return vueNews;
}

Notez la réutilisation de la vue si elle existe déjà. Android tente de réutiliser le plus possible des éléments créés au préalable afin d’économiser la batterie.

Modifions maintenant notre code de l’activité principale pour remplir une liste de news, initialiser l’adapter créé, l’attacher à notre ListView et afficher l’url du site en cliquant sur l’une des lignes de la liste :

/**
* Tutoriel n°2 dans le cadre du blog Randoomz<br />
* Liste d'affichage des news
*
* @author Gerard
*/
public class Randoomz_TutoActivity extends ListActivity {
    private AdapterListeNews adapter;
    private List<News> items = new ArrayList<News>();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Initialisation de la liste des items
        items.add(new News(1, "Randoomz", "Site web de randoomz", "www.randoomz.org"));
        items.add(new News(2, "Google", "Site web de google", "www.google.be"));
        items.add(new News(3, "Apple", "Site web de Apple", "www.apple.fr"));
        items.add(new News(4, "Facebook", "Site web de Facebook", "www.facebook.com"));
        items.add(new News(5, "Twitter", "Site web de twitter", "www.twitter.com"));

        // Initialise l'adapter et l'ahoute à la ListView
        this.adapter = new AdapterListeNews(this, items);
        this.setListAdapter(adapter);

        // Récupère la ListView
        ListView lv = getListView();
        // Ajoute une action au clique sur un élément de la liste
        lv.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                // Affiche un "Toast" avec l'url de l'élément sélectionné
                Toast.makeText(getApplicationContext(),((VueNews) view).getNews().getUrl(), Toast.LENGTH_SHORT).show();
            }
        });
    }
}

Nous finissons avec cela notre second tutoriel. Son résultat final ne correspond pas du tout à l’apparence de notre application final. Nous verrons au prochain tutoriel comment changer le fond de notre « Activity », le fond de chacune des lignes et programmer de façon à bien détacher les différentes ressources.

 

Codes sources du Tutoriel Android n°2

ListeView contenant des vues simples

Si vous suivez un peu l’actualité du blog, vous aurez noté la sortie prochaine de l’application officielle de Randoomz (actuellement en alpha). L’idée était de libérer le code source une fois la version alpha à l’état stable.

La chose a un peu changé. N’ayez crainte, le code source sera bien libéré mais pas directement une fois l’application à l’état stable. Je compte fournir des tutoriels expliquant le code de l’application petit à petit. Cela permettra d’aider les débutants et, peut-être, d’apprendre certaines choses aux plus expérimentés.

Le tutoriel d’aujourd’hui porte sur les ListView. Il s’agit d’un « layout » un peu particulier qui vous permet d’afficher une liste de vues. C’est sans doute l’un des « layout » les plus utilisés dans les systèmes Android.

Dans ce premier tutoriel, je présenterai la ListView avec de simples vues pour commencer doucement. Un autre traitera sur la personnalisation de ses vues pour en afficher des plus complexes.

Commençons !

Après avoir créé votre projet android, éditez le fichier res > layout > main.xml en remplaçant son contenu par :

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   android:text="" />

Ce « layout » sera utilisé pour s’appliquer à chaque élément de notre futur ListView.

Maintenant, ouvrez la classe qui a été créé automatique à la création de votre projet. Il y a plusieurs façon d’intégrer une ListView dans une activité. Dans notre cas, nous allons utiliser une méthode qui restreint son contenu puisqu’il ne peut y avoir qu’une ListView.

A la place de Activity, étendez votre classe par ListActivity :

public class Randoomz_TutoActivity extends ListActivity {

Complétez la méthode onCreate de la classe :

@Override
public void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   // Ajoute automatiquement une ListView qui remplit l'entièreté de l'écran
   this.setListAdapter(new ArrayAdapter<String>(this, R.layout.main, items));
   // Récupère la ListView
   ListView lv = getListView();
   // Ajoute une action au clique sur un élément de la liste
   lv.setOnItemClickListener(new OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> parent, View view,
         int position, long id) {
         // Affiche un "Toast" avec le texte de l'élément sélectionné
         Toast.makeText(getApplicationContext(), ((TextView) view).getText(), Toast.LENGTH_LONG).show();
      }
   });
}

Attardons nous un peu sur la méthode setListAdapter. Cette méthode prend en paramètre l’instance d’un ArrayAdapter. Le constructeur de cette classe prend 3 paramètres :

  1. Le contexte de la classe
  2. Une ressource
  3. Une liste d’objets

Notre liste d’objet est en faite un simple tableau de String puisque le type générique de ArrayAdapter est de ce même type :

private String[] items = { "Belgique", "France", "Espagne", "Italie", "Angleterre", "Irlande" };

Bien entendu, le contenu du tableau peut être différent du moment qu’il s’agit d’un tableau de String.

Nous avons fini notre application. Vous devriez avoir le résultat suivant :

 

Codes sources du Tutoriel Android n°1