mardi 20 septembre 2011

Utilisation du ArrayAdapter avec les ListView - partie 2

Bon, maintenant que l'on a fait notre Adapteur générique pour les ListView, on va pouvoir le triturer un peu, pour voir (cf partie 1, article précédent).


Ce qui me perturbe un peu dans cet adapteur, c'est cette histoire d'id de resource qui est passé dans le constructeur, et que l'on doit rappeler dans l'override du GetView. C'est pas consistant, cette affaire. D'après moi, cela ne sert à rien de passer un id dans le constructeur, si on est obligé de le re-spécifier dans l'override. 


On va vérifier...


Je reprends mon code de l'article précédent :



{
   [Activity (Label = "BasicAdapterUsage", MainLauncher = true)]         
   public class BasicAdapterUsage : Activity
   {
      
      public interface iViewItem
      {
         void FillView(View v);
      }
      
      public class Moto : iViewItem
      {
         public string Name;
         public string Pays;
         
         
         public Moto(string name, string pays)
         {
            Name = name;
            Pays = pays;
         }
         
         public void FillView(View v) 
         {
            TextView txtName = v.FindViewById<TextView>(Resource.Id.txtName);
            TextView txtPays = v.FindViewById<TextView>(Resource.Id.txtPays);
            if(txtName==null || txtPays==null)
               throw new Exception("Can't find resources for element of class Moto");
            txtName.Text = this.Name;
            txtPays.Text = this.Pays;
         }
      }
      
      
      List<Moto> items;
      protected override void OnCreate (Bundle bundle)
      {
         base.OnCreate (bundle);
         
         // Set our view from the "main" layout resource
         SetContentView (Resource.Layout.BasicView);
         
         // Settings data
         items = new List<Moto>();
         Moto yamaha = new Moto("Yamaha","Japon");
         Moto ducati = new Moto("Ducati","Italie");
         Moto bmw = new Moto("BMW","Allemagne");
         items.Add(yamaha);items.Add(ducati);items.Add(bmw);


         // Settings ListView
         ListView listOfMotorCycle = FindViewById<ListView>(Resource.Id.listMotorCycle);
         BasicAdapter<Moto> myAdapter = new BasicAdapter<Moto>(this,Resource.Layout.Moto,items);
         listOfMotorCycle.Adapter = myAdapter;
         Button btnAdd = FindViewById<Button>(Resource.Id.btnAdd);
         btnAdd.Click+=delegate{
            Moto triumph = new Moto("Triumph","Angleterre");
            myAdapter.Add(triumph);
            myAdapter.NotifyDataSetChanged();
         };
      }
      
      public class BasicAdapter<T> : ArrayAdapter<T> where T : iViewItem
      {
         
         private int itemViewResourceId;
         
         public  BasicAdapter(Context context, int textViewResourceId, List<T> data) :base(context, textViewResourceId, data)
         {
            itemViewResourceId = textViewResourceId;
         }
          public override View GetView (int position, View convertView, ViewGroup parent) 
         {
               View v = convertView;
               if (v == null) 
            {
                   LayoutInflater vi = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
               v = vi.Inflate(itemViewResourceId,null);
               }
            if(position<this.Count) 
            {
                   T o = this.GetItem(position);
                   if (o != null) 
                  o.FillView(v);
            }
               return v;
          }
      }


   }
}



On va enlever le itemViewResourceId du BasicAdapter, et faire supporter cet id par les instances de la classe moto directement...


L'interface iViewItem et la  classe moto deviennent :



public interface iViewItem
{
   int ResourceId {get;set;}
   void FillView(View v);
}


public class Moto : iViewItem
{
   public string Name;
   public string Pays;
   
   public int ResourceId {get;set;}
   
   public Moto(string name, string pays, int resourceId)
   {
      Name = name;
      Pays = pays;
      ResourceId = resourceId;
   }
   
   public void FillView(View v) 
   {
      TextView txtName = v.FindViewById<TextView>(Resource.Id.txtName);
      TextView txtPays = v.FindViewById<TextView>(Resource.Id.txtPays);
      if(txtName==null || txtPays==null)
         throw new Exception("Can't find resources for element of class Moto");
      txtName.Text = this.Name;
      txtPays.Text = this.Pays;
   }
}

Le BasicAdapter devient :


public class BasicAdapter<T> : ArrayAdapter<T> where T : iViewItem
{
   
   public  BasicAdapter(Context context, int textViewResourceId, List<T> data) :base(context, textViewResourceId, data)
   {
   }


   public override View GetView (int position, View convertView, ViewGroup parent) 
   {
      View v = convertView;
      T o = this.GetItem(position);
      if (v == null && o!=null) 
      {
         LayoutInflater vi = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
         v = vi.Inflate(o.ResourceId,null);
      }
      if(position<this.Count) 
      {
         if (o != null) 
         o.FillView(v);
      }
      return v;
   }
}


Dans GetView, on modifie un peu le code pour récupérer l'id de la ressource liée à l'instance que l'on manipule.

Et enfin, les modifs dans le "main" 

- les instances de moto sont modifiées :

Moto yamaha = new Moto("Yamaha","Japon", Resource.Layout.Moto);
Moto ducati = new Moto("Ducati","Italie", Resource.Layout.Moto);
Moto bmw = new Moto("BMW","Allemagne", Resource.Layout.Moto);


Et, la ligne intéressante de l'article : 
BasicAdapter<Moto> myAdapter = new BasicAdapter<Moto>(this,-1,items);




Et toc ! Compilation sans problème, et exécution sans erreur : ça marche !!! l'Id passé dans le constructeur ne sert à rien, apparemment !!! 


tiens, tiens, du coup, on peut essayer un autre truc...


Définissons une classe porteNaouak (parce que ma moto à moi, c'est un peu portenaouak  :-)



public class PorteNaouak : iViewItem
{
   public string BlaBla;
   
   public int ResourceId {get;set;}
   
   public PorteNaouak(string blabla, int resourceId)
   {
      BlaBla = blabla;
      ResourceId = resourceId;
   }
   
   public void FillView(View v) 
   {
      TextView txtBlabla = v.FindViewById<TextView>(Resource.Id.txtBlabla);
      txtBlabla.Text = this.BlaBla;
   }
}

et, dans le répertoire layout, un Blabla.xml


<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
>
<TextView 
android:id="@+id/txtBlabla"
android:layout_width="wrap_content"  
android:layout_height="wrap_content"  
android:padding="10dp"  
android:textSize="16sp"
/>
</RelativeLayout>


Changeons le main, et la déclaration de la liste d'item, pour qu'elle accepte aussi bien les motos que les portenaouak...


List<iViewItem> items;
protected override void OnCreate (Bundle bundle)
{
   base.OnCreate (bundle);
   
   // Set our view from the "main" layout resource
   SetContentView (Resource.Layout.BasicView);
   
   // Settings data
   items = new List<iViewItem>();
   Moto yamaha = new Moto("Yamaha","Japon", Resource.Layout.Moto);
   Moto ducati = new Moto("Ducati","Italie", Resource.Layout.Moto);
   Moto bmw = new Moto("BMW","Allemagne", Resource.Layout.Moto);
   PorteNaouak pasUneMoto = new PorteNaouak("Je suis pas une moto",Resource.Layout.Blabla);
   items.Add(yamaha);items.Add(ducati);items.Add(pasUneMoto);items.Add(bmw);


   // Settings ListView
   ListView listOfMotorCycle = FindViewById<ListView>(Resource.Id.listMotorCycle);
   BasicAdapter<iViewItem> myAdapter = new BasicAdapter<iViewItem>(this,-1,items);
   listOfMotorCycle.Adapter = myAdapter;
   Button btnAdd = FindViewById<Button>(Resource.Id.btnAdd);
   btnAdd.Click+=delegate{
      Moto triumph = new Moto("Triumph","Angleterre", Resource.Layout.Moto);
      myAdapter.Add(triumph);
      myAdapter.NotifyDataSetChanged();
   };
}


Notre liste d'items est devenue une list<iViewItem>, on a mis un portenaouak dedans, on a défini son layout comme étant Blabla, et l'instanciation du BasicAdapter est devenue :

BasicAdapter<iViewItem> myAdapter = new BasicAdapter<iViewItem>(this,-1,items);

Compilation, normale, exécution... normal aussi :



hum... il est pas mal ce composant listview, je trouve... Et si je clique sur le bouton ? Ah, ça plante : "Can't find resources for element of class Moto". 
Si on trace l'exécution, on voit que le code plante à l'affichage du portenaouak : la vue passée à l'appel du getView est celle qui correspond à l'instance d'une moto, alors qu'il manipule un portenaouak. Ok, un test en plus et on résout le problème...



public override View GetView (int position, View convertView, ViewGroup parent) 
{
   View v = convertView;
   T o = this.GetItem(position);


   if(o!=null)
   {
      if(v!=null && v.Id!=o.ResourceId)
         v = null;
      if (v == null) 
      {
         LayoutInflater vi = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
         v = vi.Inflate(o.ResourceId,null);
      }
      if(position<this.Count) 
      {
      if (o != null) 
         o.FillView(v);
      }
   }
   return v;
}


On vérifie juste que l'identifiant de la vue passée en paramètre à GetView correspond bien à celui de l'instance que l'on manipule. Et là, ça marche !


Le code complet :


namespace GenericAdapter
{
   [Activity (Label = "BasicAdapterUsage", MainLauncher = true)]         
   public class BasicAdapterUsage : Activity
   {
      
      public interface iViewItem
      {
         int ResourceId {get;set;}
         void FillView(View v);
      }
      
      public class Moto : iViewItem
      {
         public string Name;
         public string Pays;
         
         public int ResourceId {get;set;}
         
         public Moto(string name, string pays, int resourceId)
         {
            Name = name;
            Pays = pays;
            ResourceId = resourceId;
         }
         
         public void FillView(View v) 
         {
            TextView txtName = v.FindViewById<TextView>(Resource.Id.txtName);
            TextView txtPays = v.FindViewById<TextView>(Resource.Id.txtPays);
            if(txtName==null || txtPays==null)
               throw new Exception("Can't find resources for element of class Moto");
            txtName.Text = this.Name;
            txtPays.Text = this.Pays;
         }
      }
      
      public class PorteNaouak : iViewItem
      {
         public string BlaBla;
         
         public int ResourceId {get;set;}
         
         public PorteNaouak(string blabla, int resourceId)
         {
            BlaBla = blabla;
            ResourceId = resourceId;
         }
         
         public void FillView(View v) 
         {
            TextView txtBlabla = v.FindViewById<TextView>(Resource.Id.txtBlabla);
            txtBlabla.Text = this.BlaBla;
         }
      }
      
      
      List<iViewItem> items;
      protected override void OnCreate (Bundle bundle)
      {
         base.OnCreate (bundle);
         
         // Set our view from the "main" layout resource
         SetContentView (Resource.Layout.BasicView);
         
         // Settings data
         items = new List<iViewItem>();
         Moto yamaha = new Moto("Yamaha","Japon", Resource.Layout.Moto);
         Moto ducati = new Moto("Ducati","Italie", Resource.Layout.Moto);
         Moto bmw = new Moto("BMW","Allemagne", Resource.Layout.Moto);
         PorteNaouak pasUneMoto = new PorteNaouak("Je suis pas une moto",Resource.Layout.Blabla);
         items.Add(yamaha);items.Add(ducati);items.Add(pasUneMoto);items.Add(bmw);


         // Settings ListView
         ListView listOfMotorCycle = FindViewById<ListView>(Resource.Id.listMotorCycle);
         BasicAdapter<iViewItem> myAdapter = new BasicAdapter<iViewItem>(this,-1,items);
         listOfMotorCycle.Adapter = myAdapter;
         Button btnAdd = FindViewById<Button>(Resource.Id.btnAdd);
         btnAdd.Click+=delegate{
            Moto triumph = new Moto("Triumph","Angleterre", Resource.Layout.Moto);
            myAdapter.Add(triumph);
            myAdapter.NotifyDataSetChanged();
         };
      }
      
      public class BasicAdapter<T> : ArrayAdapter<T> where T : iViewItem
      {
          
         public  BasicAdapter(Context context, int textViewResourceId, List<T> data) :base(context, textViewResourceId, data)
         {
         }


         public override View GetView (int position, View convertView, ViewGroup parent) 
{
   View v = convertView;
   T o = this.GetItem(position);


   if(o!=null)
   {
      if(v!=null && v.Id!=o.ResourceId)
         v = null;
      if (v == null) 
      {
         LayoutInflater vi = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
         v = vi.Inflate(o.ResourceId,null);
      }
      if(position<this.Count) 
      {
      if (o != null) 
         o.FillView(v);
      }
   }
   return v;
}
      }


   }
}



Le layout principal


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
   
   <Button  
    android:id="@+id/btnAdd"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/Add"
    />


   <ListView 
         android:id="@+id/listMotorCycle"
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"
   />
   
</LinearLayout>


le fichier de ressource moto.xml


<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
>
   <TextView 
      android:id="@+id/txtName"   
       android:layout_width="wrap_content"  
       android:layout_height="wrap_content"  
       android:padding="10dp"  
       android:textSize="16sp"
   >  
   </TextView>
   <TextView 
      android:id="@+id/txtPays"   
       android:layout_width="wrap_content"  
       android:layout_height="wrap_content"  
       android:padding="10dp"  
       android:textSize="12sp"
      android:layout_toRightOf="@id/txtName"
   >  
   </TextView>
   
</RelativeLayout>   


le fichier de resource blabla.xml


<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
>
   <TextView 
      android:id="@+id/txtBlabla"   
       android:layout_width="wrap_content"  
       android:layout_height="wrap_content"  
       android:padding="10dp"  
       android:textSize="16sp"
   >  
   </TextView>
</RelativeLayout>








Utilisation du ArrayAdapter avec les ListView

L'un des composants les plus utilisés dans les applications Android est le ListView. A priori facilement utilisable, il permet d'afficher une liste d'éléments en quelques lignes de codes.


Cependant, si on veut afficher autre chose qu'une simple liste dont chaque élément est une chaine de caractères, il faut travailler un peu plus. Et, d'après ce que j'en ai vu sur le web, c'est là que les choses se compliquent pour certains.  
Je vais reprendre l'implémentation typique, et on va un peu l'améliorer, pour la rendre plus générique. Ensuite, on jouera un peu avec le composant...


Commençons par définir une classe qui représentera les éléments à afficher...



public class Moto
{
      public string Name;
      public string Pays;


      public Moto(string name, string pays)
      {
            Name = name;
            Pays = pays;
      }
}


Le main activity a classiquement cette implémentation  :



namespace GenericAdapter
{
  [Activity (Label = "BasicAdapterUsage", MainLauncher = true)]
  public class BasicAdapterUsage : Activity
  {
     List<Moto> items;


     protected override void OnCreate (Bundle bundle)
     {
        base.OnCreate (bundle);
     // Set our view from the "main" layout resource
        SetContentView (Resource.Layout.BasicView);
     // Settings data
        items = new List<Moto>();
        Moto yamaha = new Moto("Yamaha","Japon");
        Moto ducati = new Moto("Ducati","Italie");
        Moto bmw = new Moto("BMW","Allemagne");
        items.Add(yamaha);items.Add(ducati);items.Add(bmw);
        // Settings ListView
        ListView listOfMotorCycle = FindViewById<ListView>(Resource.Id.listMotorCycle);
        MotoAdapter myAdapter = new MotoAdapter(this,Resource.Layout.Moto,items);
        listOfMotorCycle.Adapter = myAdapter;
        Button btnAdd = FindViewById<Button>(Resource.Id.btnAdd);
        btnAdd.Click+=delegate{
           Moto triumph = new Moto("Triumph","Angleterre");
           myAdapter.Add(triumph);
           myAdapter.NotifyDataSetChanged();
        };
     }
  }
}


Le layout associé sera basiquement un bouton pour ajouter un élément et une listview verticale : 
>> fichier BasicView.axml dans le répertoire resources.layout


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<Button
android:id="@+id/btnAdd"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/Add"  
/>
<ListView 
android:id="@+id/listMotorCycle"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>


Maintenant, passons aux choses plus intéressantes, avec la surcharge du ArrayAdapter utilisé (MotoAdapter, dans le code du "main")


public class MotoAdapter : ArrayAdapter<Moto>
{
   public  MotoAdapter(Context context, int textViewResourceId, IList<Moto> data) :base(context, textViewResourceId, data)
   {
   }
   public override View GetView (int position, View convertView, ViewGroup parent) 
   {
      View v = convertView;
      if (v == null) 
      {
         LayoutInflater vi = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
         v = vi.Inflate(Resource.Layout.Moto, null);
      }
      if(position<this.Count) 
      {
         Moto aMoto = this.GetItem(position);
         if (aMoto != null) 
         {
            
            TextView name = (TextView)v.FindViewById(Resource.Id.txtName);
            if (name != null) 
            {
               name.Text = aMoto.Name;                           
            }
            TextView pays = (TextView)v.FindViewById(Resource.Id.txtPays);
            if (pays != null) 
            {
               pays.Text = aMoto.Pays;                           
            }


         }
      }
          return v;
   }
}




Pour finir, Le layout utilisé pour chaque élément de la liste (Moto, dans le code du main)


>> fichier Moto.xml dans le répertoire resources.layout


<?xml version="1.0" encoding="utf-8"?> 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
>
 <TextView 
      android:id="@+id/txtName"   
       android:layout_width="wrap_content"  
       android:layout_height="wrap_content"  
       android:padding="10dp"  
       android:textSize="16sp"
/>
<TextView 
      android:id="@+id/txtPays"   
      android:layout_width="wrap_content"  
      android:layout_height="wrap_content"  
      android:padding="10dp"  
      android:textSize="12sp"
      android:layout_toRightOf="@id/txtName"
/>
</RelativeLayout> 


Voilà, tout est défini. Les lignes intéressantes sont d'une part, l'instanciation de l'Adapter de la listView, et d'autre part, la surcharge de l'ArrayAdapter et l'ajout d'éléments à la liste.


L'adapter fourni à la listView est instancié de la façon suivante : 

MotoAdapter myAdapter = new MotoAdapter(this,Resource.Layout.Moto,items);
listOfMotorCycle.Adapter = myAdapter;

On indique à l'adapteur quel View utiliser pour l'affichage de chaque item, et on donne la source des données.


Dans le constructeur, on ne fait qu'un appel au constructeur de la classe mère.


public  MotoAdapter(Context context, int textViewResourceId, IList<Moto> data) :base(context, textViewResourceId, data)
{
}



Et, la raison qui fait que l'on est obligé de surcharger l'ArrayAdapter, l'override du GetView ;
La méthode est appelée pour l'affichage de chaque élément de la liste.



   public override View GetView (int position, View convertView, ViewGroup parent) 
   {
      View v = convertView;
      if (v == null) 
      {
         LayoutInflater vi = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
         v = vi.Inflate(Resource.Layout.Moto, null);
      }
      if(position<this.Count) 
      {
         Moto aMoto = this.GetItem(position);
         if (aMoto != null) 
         {
            
            TextView name = (TextView)v.FindViewById(Resource.Id.txtName);
            if (name != null) 
            {
               name.Text = aMoto.Name;                           
            }
            TextView pays = (TextView)v.FindViewById(Resource.Id.txtPays);
            if (pays != null) 
            {
               pays.Text = aMoto.Pays;                           
            }


         }
      }
          return v;
   }

Il faut noter un petit point stupide dans le code ci-dessus : la ligne pour récupérer la View pour l'item en cours :
 v = vi.Inflate(Resource.Layout.Moto, null);
Apparemment, il n'y a aucun moyen prévu pour récupérer nativement l'id de la vue (alors qu'il est passé en paramètre dans le constructeur). Soit c'est une erreur de jeunesse du framework, soit j'ai loupé un truc... On corrigera ça plus tard, quand on rendra l'adapter générique.

Le dernier point à noter dans cet exemple concerne l'ajout d'éléments dans la liste : il faut faire l'ajout via l'adapter, et non directement dans la source de données :

btnAdd.Click+=delegate{
           Moto triumph = new Moto("Triumph","Angleterre");
           myAdapter.Add(triumph);
           myAdapter.NotifyDataSetChanged();
        };
Après l'ajout, il ne faut pas oublier de notifier au composant ListView que les données ont changés :

     myAdapter.NotifyDataSetChanged();

Execution, hop, ça donne quelque chose comme ça :


avant de cliquer :


après un click :


Bon, maintenant que l'on a notre canevas de ListView qui fonctionne, on peut rendre notre adapter un peu plus générique.
La seule chose à gérer, finalement, est la méthode GetView. Pas trop compliqué.

Déjà, passons en type générique :


public class BasicAdapter<T> : ArrayAdapter<T> 
{
   
private int itemViewResourceId;
   public  BasicAdapter(Context context, int textViewResourceId, List<T> data) :base(context, textViewResourceId, data)
{
itemViewResourceId = textViewResourceId;

}

   public override View GetView (int position, View convertView, ViewGroup parent) 
   {
      View v = convertView;
      if (v == null) 
      {
         LayoutInflater vi = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
         v = vi.Inflate(itemViewResourceId,null);
      }
      if(position<this.Count) 
      {
         T o = this.GetItem(position);
         if (o != null) 
           doSomething();// .... remplir la view
      }
    return v;
   }
}

Pratiquement rien à faire : on déclare un int pour stocker l'id de la resource qui sera utilisée pour l'affichage dans le GetView.
Et pour remplir la vue, une interface toute simple fera l'affaire...

public interface iViewItem { void FillView(View v); }

au final, notre adapteur MotoAdapter devient un BasicAdapter<T>:



public class BasicAdapter<T> : ArrayAdapter<T> where T : iViewItem
{
         
   private int itemViewResourceId;
         
   public  BasicAdapter(Context context, int textViewResourceId, List<T> data) :base(context, textViewResourceId, data)
   {
      itemViewResourceId = textViewResourceId;
   }
          
   public override View GetView (int position, View convertView, ViewGroup parent) 
   {
      View v = convertView;
      if (v == null) 
      {
         LayoutInflater vi = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
         v = vi.Inflate(itemViewResourceId,null);
      }
      if(position<this.Count) 
      {
         T o = this.GetItem(position);
         if (o != null) 
            o.FillView(v);
      }
      return v;
   }
}





et la classe Moto :



public class Moto : iViewItem
{
   public string Name;
   public string Pays;
   
   
   public Moto(string name, string pays)
   {
      Name = name;
      Pays = pays;
   }
   
   public void FillView(View v) 
   {
      TextView txtName = v.FindViewById<TextView>(Resource.Id.txtName);
      TextView txtPays = v.FindViewById<TextView>(Resource.Id.txtPays);
      if(txtName==null || txtPays==null)
         throw new Exception("Can't find resources for element of class Moto");
      txtName.Text = this.Name;
      txtPays.Text = this.Pays;
   }
}



Le code du main change un petit peu :
au lieu de :
MotoAdapter myAdapter = new MotoAdapter(this,Resource.Layout.Moto,items);
on a :
BasicAdapter<Moto> myAdapter = new BasicAdapter<Moto>(this,Resource.Layout.Moto,items);


Le code complet (toutes les classes sont dans le même fichier)...



namespace GenericAdapter
{
   [Activity (Label = "BasicAdapterUsage", MainLauncher = true)]         
   public class BasicAdapterUsage : Activity
   {
      
      public interface iViewItem
      {
         void FillView(View v);
      }
      
      public class Moto : iViewItem
      {
         public string Name;
         public string Pays;
         
         
         public Moto(string name, string pays)
         {
            Name = name;
            Pays = pays;
         }
         
         public void FillView(View v) 
         {
            TextView txtName = v.FindViewById<TextView>(Resource.Id.txtName);
            TextView txtPays = v.FindViewById<TextView>(Resource.Id.txtPays);
            if(txtName==null || txtPays==null)
               throw new Exception("Can't find resources for element of class Moto");
            txtName.Text = this.Name;
            txtPays.Text = this.Pays;
         }
      }
      
      
      List<Moto> items;
      protected override void OnCreate (Bundle bundle)
      {
         base.OnCreate (bundle);
         
         // Set our view from the "main" layout resource
         SetContentView (Resource.Layout.BasicView);
         
         // Settings data
         items = new List<Moto>();
         Moto yamaha = new Moto("Yamaha","Japon");
         Moto ducati = new Moto("Ducati","Italie");
         Moto bmw = new Moto("BMW","Allemagne");
         items.Add(yamaha);items.Add(ducati);items.Add(bmw);


         // Settings ListView
         ListView listOfMotorCycle = FindViewById<ListView>(Resource.Id.listMotorCycle);
         BasicAdapter<Moto> myAdapter = new BasicAdapter<Moto>(this,Resource.Layout.Moto,items);
         listOfMotorCycle.Adapter = myAdapter;
         Button btnAdd = FindViewById<Button>(Resource.Id.btnAdd);
         btnAdd.Click+=delegate{
            Moto triumph = new Moto("Triumph","Angleterre");
            myAdapter.Add(triumph);
            myAdapter.NotifyDataSetChanged();
         };
      }
      
      public class BasicAdapter<T> : ArrayAdapter<T> where T : iViewItem
      {
         
         private int itemViewResourceId;
         
         public  BasicAdapter(Context context, int textViewResourceId, List<T> data) :base(context, textViewResourceId, data)
         {
            itemViewResourceId = textViewResourceId;
         }
          public override View GetView (int position, View convertView, ViewGroup parent) 
         {
               View v = convertView;
               if (v == null) 
            {
                   LayoutInflater vi = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
               v = vi.Inflate(itemViewResourceId,null);
               }
            if(position<this.Count) 
            {
                   T o = this.GetItem(position);
                   if (o != null) 
                  o.FillView(v);
            }
               return v;
          }
      }


   }
}

voilà, c'est fait. Ca fonctionne, on n'a pas besoin d'écrire trop de code à chaque nouvelle liste, et c'est assez simple.

La suite dans un autre post...