2012-04-03

Remplir une ListView avec un ArrayList<String> sans passer par une ListActivity

Mon projet du mois fait une utilisation intensive du chipset GPS et du RIL du smartphone. Globalement tout se passe comme prévu malgré la diversité des précisions des chipset et l'endroit où se trouve le terminal. Globalement, parce que parfois ça marche pas. Alors que chez moi ça marche™.

Le casse-tête de la journée est de trouver une manière aussi simple que possible d'aider les testeurs en leur proposant de visionner un journal d'activité de l'application. Comment remplir le plus simplement possible une ListView avec un ArrayList<String> ?

Layout

La partie la plus simple du casse-tête est sans aucun doute la déclaration de la ListView dans le layout qui nous interesse.
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
   ...
        <ListView
            android:id="@+id/lvLog"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >
        </ListView>
   ...
</merge>

ArrayList

La déclaration de notre ArrayList<String> de test est également assez simple :
ArrayList<String> als = new ArrayList<String>();
als.add("dim");
als.add("dam");
als.add("doum");

Déclaration du remplissage

Le morceaux le plus technique du casse-tête est encore devant mais il est possible de déclarer l'inconnu :
 private void lvRefresh() {
  ListView list1 = (ListView) findViewById(R.id.lvLog);
  LogAdapter logAdapter = new LogAdapter(als);
  list1.setAdapter(logAdapter);

 }

A ce stade le code ne se compile plus parce que logAdapter n'est pas encore déclaré. C'est que nous allons faire MAINTENANT !

ListView

Contrairement à ce que pourrait nous laisser croire la vue de la ListView à travers le Graphical Layout du plug-in Eclipse, la ListView ne contient PAS de vue de base.

ListView est à prendre au sens littéral : une liste de vues. Des vues non définies.

Il est vraiment nécessaire de comprendre cette "indéfinition" des vues pour construire notre adapter : il faudra créer une vue pour chaque ligne de journal.

logAdapter

La classe LogAdapter hérite de BaseAdapter. Eclipse se propose de déclarer les méthodes manquantes :
private class LogAdapter extends BaseAdapter {
 @Override
 public int getCount() {
  // TODO Auto-generated method stub
  return 0;
 }

 @Override
 public Object getItem(int arg0) {
  // TODO Auto-generated method stub
  return null;
 }

 @Override
 public long getItemId(int arg0) {
  // TODO Auto-generated method stub
  return 0;
 }

 @Override
 public View getView(int arg0, View arg1, ViewGroup arg2) {
  // TODO Auto-generated method stub
  return null;
 }
}
Notre code compile. Youpi !
Notre LogAdapter pourrait utiliser une variable globale, mais ce ne sera pas le cas dans cet exemple. Je vais donc déclarer une variable private stringList et un constructeur à cet endroit là :
private class LogAdapter extends BaseAdapter {
 private ArrayList stringList;

 public LogAdapter(ArrayList arraylistLog) {
  // TODO Auto-generated constructor stub
  this.stringList = arraylistLog;
 }
Les méthodes getItem et getItemId sont assez triviales. La première renvoie le Xième élément et la deuxième donne la position d'un élément dans l'ensemble. On rajoute également getCount() pour la route :
 @Override
 public int getCount() {
  // TODO Auto-generated method stub
  return stringList.size();
 }

 @Override
 public Object getItem(int arg0) {
  // TODO Auto-generated method stub
  return stringList.get(arg0);
 }

 @Override
 public long getItemId(int arg0) {
  // TODO Auto-generated method stub
  return arg0;
 }
Nous voila au sommet de la colline :
 @Override
 public View getView(int arg0, View arg1, ViewGroup arg2) {
  // TODO Auto-generated method stub
  TextView v = (TextView) new TextView(getBaseContext());
  v.setTextColor(0xff000000);
  v.setText(this.stringList.get(arg0));
  return v;
 }

Recyclage

Il est dommage recréer une View lorsque l'utilisateur monte ou descend la liste : une View créée et une vue détruite. Android permet de recycler les vues existantes avec setTag()/getTag().
 @Override
 public View getView(int arg0, View arg1, ViewGroup arg2) {
  if (arg1 == null) {
   //Log.d(TAG,"NOUVEAU getView " + arg0 + " " + arg1 );
   arg1 = (TextView) new TextView(getBaseContext());
   ((TextView) arg1).setTextColor(0xff000000);
   arg1.setTag(arg1);
  } else {
   //Log.d(TAG,"RECYCLAGE getView " + arg0 + " " + arg1 );
   arg1 = (View) arg1.getTag();
  }
  ((TextView) arg1).setText(this.stringList.get(arg0));
  return arg1;
 }
Si cet aspect recyclage vous intéresse, il y a l'excellent article de Amber Fog

Aucun commentaire: