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"
/>
<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;
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();
avant de cliquer :
après un click :
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...
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...


Aucun commentaire:
Enregistrer un commentaire