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>


Aucun commentaire:
Enregistrer un commentaire