Aller au contenu

association item listView


l-amoureu

Recommended Posts

Bonjour

J'ai créé une listView qui contient X item, chaque item comportant une image, et des champs text, tous téléchargé sur le net, et obtenu dans un json (les champs text ainsi que l'url sont dans le Json)

Pour empêcher l'attente, j'ai threadé le tout.

D'abord, le téléchargement du json, qui une fois fait, associe directement chaque item au text associé.

Et, une fois le téléchargement du Json terminé, un nouveau thread est lancé pour télécharger les image a partir de l'url contenu dans le Json (en attendant que le téléchargement et l'association a l'imageView se fasse, je met une image de base)

Sauf que je me suis rendu compte que j'ai parfois un probleme d'affectation de l'image (téléchargé dans un thread donc) avec l'item.

En faisant un jeu de test (avec des Log) au niveau de l'appel du thread de téléchargement des image, et a l'interieur du thread, je me suis rendu compte que le holder que je lui passe en paramètre est toujours le même, j'ai donc des problème d'affectation au mauvais item

private class DownloadFilesTask extends AsyncTask<ViewHolder, Integer, Long>  {

	ViewHolder mHolder;
	String mImage;

	@Override
     protected Long doInBackground(ViewHolder... holders) {
		try {
			mHolder = holders[0];
			int position = holders[0].position;
			mImage = vins.get(position).url_small;
			Bitmap image = telechargerImage(mImage);
			Log.i("bitmap + holder apres", "bitmap : "+vins.get(position).url_small + " "+mHolder.toString());
			bitmaps.remove(mImage);
			bitmaps.put(mImage, image);
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
     }

	@Override
	protected void onPostExecute(Long result) {
		super.onPostExecute(result);
		try {
			mHolder.image.setImageBitmap(bitmaps.get(mImage));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}		
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
	ViewHolder holder = null;

	if (convertView == null)
	{
		convertView = myInflater.inflate(R.layout.item_vin, null);
		holder = new ViewHolder();
		holder.image = (ImageView) convertView.findViewById(R.id.imagePetiteDuVin);
		holder.nom = (TextView) convertView.findViewById(R.id.nomDuChateau);
		holder.appellation = (TextView) convertView.findViewById(R.id.appellationDuVin);
		holder.prix = (TextView) convertView.findViewById(R.id.prixDuVins);

		convertView.setTag(holder);
	} else {
		holder = (ViewHolder) convertView.getTag();
	}

	try {
		holder.nom.setText(vins.get(position).nom);
		holder.appellation.setText(vins.get(position).appellation);
		holder.prix.setText("Prix : "+vins.get(position).prix);
		holder.position = position;

		Bitmap logo = BitmapFactory.decodeResource(mContext.getResources(), mContext.getResources().getIdentifier("com.max:drawable/logo_vinoreco",null,null));
		holder.image.setImageBitmap(logo);
	} catch (Exception e) {
		e.printStackTrace();
	}


	if (!bitmaps.containsKey(vins.get(position).url_small)){
		try {
			Log.i("bitmap + holder avant", "bitmap : "+vins.get(position).url_small + " "+holder.toString());
			new DownloadFilesTask().execute(holder);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	else
		holder.image.setImageBitmap(bitmaps.get(vins.get(position).url_small));

	return convertView;
}

Le problème vient donc du fait (je pense tout du moins) que mon holder possède toujours la même adresse, et vu que je le passe a mon thread qui télécharge puis associe l'image a mon ImageView (imageView qui a pu etre modifié a n'importe quel moment vu que mon holder est toujours le même), l'image est mal affecté.

Est ce qu'il y a un moyen d'empecher ca ? (donc, d'avoir une vrai nouvelle instance de mon holder)

Cordialement

Lien vers le commentaire
Partager sur d’autres sites

Tu retombes dans le même problème que lorsque tu ne threadais pas le chargement des images: ton Holder est associée à une vue d'un élément, pas à l'élément lui-même, et une instance de Holder est réutilisée pour afficher plusieurs lignes différentes au fil du scrolling de ta ListView.

De plus, il serait sans doute préférable de ne pas lancer un nouveau Thread à chaque chargement d'image, mais plutôt d'avoir un seul Thread, qui se met en attente des différents chargements d'image. Tu peux utiliser HandlerThread pour cela, avec un Handler pour communiquer avec

Lien vers le commentaire
Partager sur d’autres sites

Pour la réutilisation du holder par le systeme, je comprend maintenant.

Un truc que je comprend pas trop : la convertView est associé a un seul item de la list ? Ou est-ce comme le holder, c'est réutilisé constamment ?

J'essay, vraiment j'essay, mais je ne comprend pas comment, lorsque je lance mon thread, lui dire que le téléchargement de l'image se fera pour un item bien préci.

En fait, ce que je comprend pas, c'est que lorsque je lance mon thread, il va lancer exécuter doInBackground, qui télécharge l'image.

Et, lorsque le téléchargement est fini, le Bitmap est stocké dans la HashMap, qui sera associé a un String, l'url de l'image.

Ensuite, c'est onPostExecute qui prend la main.

Le probleme, c'est que je peux pas envoyer directement l'url de doInBackground a onPostExecute pour qu'il puisse récuperer le bon Bitmap, idem pour les valeur contenu dans le holder qui a lancé le thread (pour rappel, le handler contient l'ImageView, et les TextView de l'item).

Je suis donc obligé de créé une variable globale a la classe DownloadFilesTask qui sera par exemple le holder de lancement du thread.

Mais, c'est juste idiot, parce que ses champs (au holder) peuvent changer a n'importe quel moment vu qu'il est utilisé par d'autre.

Je ne vois donc pas du tout comment faire en sorte que mes images téléchargé soit associé au bon ImageView lorsque je fais des thread

Lien vers le commentaire
Partager sur d’autres sites

La convertView qui est passée en paramètre de getView, c'est justement une View déjà instanciée qu'Android souhaite réutiliser pour l'affichage d'un nouvel item de la liste => recyclage

Pour ton problème de chargement d'image, il faut que tu sépares le chargement de l'image de l'affichage effectif de l'image. En gardant le fonctionnement par AsyncTask, avec un nouveau thread pour tout nouveau chargement d'image, tu pourrais faire comme ceci:

      private Set<String> imageUrlsProcessed = new HashSet<String>();
      Map<String,Bitmap> bitmaps = new Hashtable<String,Bitmap>(); // Utiliser Hashtable plutot que HashMap, cette dernière n'étant pas synchronisée (problèmes potentiels lors d'accès concurrents)

       private class DownloadFilesTask extends AsyncTask<String, Void, Void>  {

           private String mImage;

               @Override
            protected void doInBackground(String... urls) {
                       try {
                               Bitmap image = telechargerImage(urls[0]);
                               bitmaps.put(urls[0], image);
                       } catch (Exception e) {
                               e.printStackTrace();
                       }
            }

               @Override
               protected void onPostExecute() {
                       // Notification de la liste du changement du contenu de l'Adapter
                       notifyDataSetChanged();
               }               
       }

       @Override
       public View getView(int position, View convertView, ViewGroup parent) {
               ViewHolder holder = null;

               if (convertView == null)
               {
                       convertView = myInflater.inflate(R.layout.item_vin, null);
                       holder = new ViewHolder();
                       holder.image = (ImageView) convertView.findViewById(R.id.imagePetiteDuVin);
                       holder.nom = (TextView) convertView.findViewById(R.id.nomDuChateau);
                       holder.appellation = (TextView) convertView.findViewById(R.id.appellationDuVin);
                       holder.prix = (TextView) convertView.findViewById(R.id.prixDuVins);

                       convertView.setTag(holder);
               } else {
                       holder = (ViewHolder) convertView.getTag();
               }

               String imageUrl = vins.get(position).url_small;
               // Verifier si l'url a déjà été traitée (ie DownloadTask déjà lancée pour cette url ?)
               if (!imageUrlsProcessed.contains(imageUrl)) {
                   imageUrlsProcessed.add(imageUrl);
                   new DownloadTask(imageUrl).execute();
               }

               try {
                       holder.nom.setText(vins.get(position).nom);
                       holder.appellation.setText(vins.get(position).appellation);
                       holder.prix.setText("Prix : "+vins.get(position).prix);
                       holder.position = position;

                       if (bitmaps.containsKey(imageUrl)){
                           // Image déjà chargée
                           holder.image.setImageBitmap(bitmaps.get(imageUrl));
                       } else {
                           // Logo par défaut
                           Bitmap logo = BitmapFactory.decodeResource(mContext.getResources(), mContext.getResources().getIdentifier("com.max:drawable/logo_vinoreco",null,null));
                           holder.image.setImageBitmap(logo);
                       }
               } catch (Exception e) {
                       e.printStackTrace();
               }

               return convertView;
       }

L'AysncTask charge l'image, et met le résultat dans la Map, et notifie ensuite (via notifyDataSetChanged) la liste que le contenu de l'adapter a changé, ce qui devrait provoquer le réaffichage de la liste

Lors de l'affichage d'un élément, on teste si on n'a pas déjà traité l'url de l'image, et le cas échéant on lance l'AsyncTask pour effectuer le chargement. Et on affiche l'image si elle est déjà chargée, le logo sinon

Pour moi, cette solution reste perfectible, car on va potentiellement lancer plusieurs threads en parallèle, il serait préférable de n'avoir qu'un thread qui séquentialise les chargements. Il faudrait utiliser HandlerThread/Handler pour ce faire, mais c'est vrai que c'est un peu plus compliqué

Lien vers le commentaire
Partager sur d’autres sites

  • 2 months later...

slt tt le monde

j'ai parser des info id nom... avec la format json via un serveur web php et j'aimerai ajouter dans mon code la partie de parser l'image avec id et nom et prix

package com.pxr.tutorial.json;

import java.util.ArrayList;
import java.util.HashMap;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;


import android.app.ListActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;

import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;

import com.pxr.tutorial.xmltest.R;

public class Restaurant extends ListActivity {
String urlresto= "http://www.monresto.net/partenaires/restaurant/";
   //TextView tv;
 @Override
   public void onCreate(Bundle savedInstanceState) {

   	super.onCreate(savedInstanceState);;
       setContentView(R.layout.listplaceholder);
       Button button = new Button(this);
	Bundle bundle = getIntent().getExtras();
	if (bundle != null) {
		button.setText("Item name = " + bundle.getString("id")
				+ " --- Go Back ");
	} else {
		button.setText("Go Back");
	}


       ArrayList<HashMap<String, String>> mylist = new ArrayList<HashMap<String, String>>();


       JSONObject json = JSONfunctions.getJSONfromURL("http://monresto.net/android/marsa.php?id_cp="+ bundle.getString("id"));

       try{

       	JSONArray  monrestodb1 = json.getJSONArray("monrestodb1");

        for(int i=0;i<monrestodb1.length();i++){						
			HashMap<String, String> map = new HashMap<String, String>();	
			JSONObject e = monrestodb1.getJSONObject(i);

			map.put("name",  String.valueOf(i));
        	map.put("name", "" + e.getString("nom"));
        	map.put("id", "" + e.getString("id"));
        	//map.put("statut", "" + e.getString("statut"));
        	//map.put("magnitude", "Magnitude: " +  e.getString("magnitude"));
        	mylist.add(map);			
		}		
       }catch(JSONException e)        {
       	 Log.e("log_tag", "Error parsing data "+e.toString());
       }

       ListAdapter adapter = new SimpleAdapter(this, mylist , R.layout.listmontplaisir, 
                       new String[] { "name", "magnitude" }, 
                       new int[] { R.id.montplaisirtext });

       setListAdapter(adapter);

       final ListView lv = getListView();
       lv.setTextFilterEnabled(true);	
       lv.setOnItemClickListener(new OnItemClickListener() {
       	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {        		
       		@SuppressWarnings("unchecked")
			HashMap<String, String> o = (HashMap<String, String>) lv.getItemAtPosition(position);	        		
       		Toast.makeText(Restaurant.this, "NAME '" + o.get("id") + "' was clicked.", Toast.LENGTH_SHORT).show(); 

       	Intent intent = new Intent(Restaurant.this, Famille_plat.class);
       		//intent.putExtra("url", "http://monresto.net/android/marsa.php");
       	String txt = o.get("id");
   		intent.putExtra("id", txt);

   		startActivity(intent);
		}


		});

       }

   }

svp aider moi

Lien vers le commentaire
Partager sur d’autres sites

Archivé

Ce sujet est désormais archivé et ne peut plus recevoir de nouvelles réponses.

×
×
  • Créer...