web-dev-qa-db-fra.com

Utilisation de AsyncTask pour charger des images dans ListView

J'ai un ListView qui peut contenir une image. Cela dépend si l'image existe ou non dans SDCARD.

Voici mon exemple de code:

public class MainActivity extends Activity  {

    ListView mListView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mListView = new ListView(this);
        setContentView(mListView);

        String[] arr = new String[] { 
                "/example/images/1.jpg", "/example/images/2.jpg",  
                "/example/images/3.jpg", "/example/images/4.jpg",  
                "/example/images/5.jpg", "/example/images/6.jpg", 
                "/example/images/7.jpg", "/example/images/8.jpg",  
                "/example/images/9.jpg", "/example/images/1.jpg", 
                "/example/images/2.jpg", "/example/images/3.jpg",  
                "/example/images/4.jpg", "/example/images/5.jpg",  
                "/example/images/6.jpg", "/example/images/7.jpg",  
                "/example/images/8.jpg", "/example/images/9.jpg", 
                "/example/images/1.jpg", "/example/images/2.jpg",  
                "/example/images/3.jpg", "/example/images/4.jpg",  
                "/example/images/5.jpg", "/example/images/6.jpg", 
                "/example/images/7.jpg", "/example/images/8.jpg",  
                "/example/images/9.jpg", "/example/images/1.jpg", 
                "/example/images/2.jpg", "/example/images/3.jpg",  
                "/example/images/4.jpg", "/example/images/5.jpg",  
                "/example/images/6.jpg", "/example/images/7.jpg",  
                "/example/images/8.jpg", "/example/images/9.jpg"}; 

        List<String> list = Arrays.asList(arr);

        MyAdapter adapter = new MyAdapter(this, R.layout.listitem_imv, list);

        mListView.setAdapter(adapter);
    }

    class MyAdapter extends ArrayAdapter<String>{

        List<String> mList;
        LayoutInflater mInflater;
        int mResource;

        public MyAdapter(Context context, int resource,
                List<String> objects) {
            super(context, resource, objects);

            mResource = resource;
            mInflater = getLayoutInflater();
            mList = objects;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view;

            if(convertView == null){
                view = mInflater.inflate(mResource, null);
            }else{
                view = convertView;
            }

            ImageView imageView = (ImageView) view.findViewById(R.id.imv);
            TextView textView = (TextView) view.findViewById(R.id.txv);

                            imageView.setTag(mList.get(position));//tag of imageView == path to image
            new LoadImage().execute(imageView);
            textView.setText(mList.get(position).toString());

            return view;
        }       
    }

    class LoadImage extends AsyncTask<Object, Void, Bitmap>{

        private ImageView imv;
        private String path;


        @Override
        protected Bitmap doInBackground(Object... params) {
            imv = (ImageView)   params[0];

            path = imv.getTag().toString();

            Bitmap bitmap = null;
            File file = new File( 
                    Environment.getExternalStorageDirectory().getAbsolutePath() + path);

            if(file.exists()){
                bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
            }

            return bitmap;
        }
        @Override
        protected void onPostExecute(Bitmap result) {
            if(result != null && imv != null){
                imv.setVisibility(View.VISIBLE);
                imv.setImageBitmap(result);
            }else{
                imv.setVisibility(View.GONE);
            }
        }
    }
}

Le répertoire 'sdcard/example/images' contient les images: 1.jpg, 2.jpg, 3.jpg, 4.jpg, 6.jpg, 7.jpg et 9.jpg . Le résultat attendu est :example

Mais, si je fais défiler rapidement la liste, certaines images sont insérées dans les éléments incorrects . Cela est dû à l'utilisation de convertView dans la méthode getView ().

Si j'utilise le code suivant, le code fonctionne bien:

        //if(convertView == null){
        //  view = mInflater.inflate(mResource, null);
        //}else{
        //  view = convertView;
        //}
        view = mInflater.inflate(mResource, null);

Lorsque la liste défile rapidement, deux tâches asynchrones peuvent référencer une même vue, en raison de l'utilisation de convertView . Comment annuler une tâche asynchrone lorsque la vue n'est plus visible? (Et est utilisée par un autre élément de ListView)

modifier

            @Override
    protected void onPostExecute(Bitmap result) {
        if(result != null && imv != null){

            if(imv.getTag().equals(path)){
                imv.setVisibility(View.VISIBLE);
                imv.setImageBitmap(result);
            }else{
                imv.setVisibility(View.GONE);
            }

        }else{
            imv.setVisibility(View.GONE);
        }
    }
15
Rodrigo

Vous pouvez envoyer ImageView au constructeur de tâches et y conserver une référence au chemin de l'image. Maintenant, sur onPostExecute, vérifiez si la balise actuelle de ImageView est identique à celle avec laquelle vous avez commencé. Si oui, alors définissez l'image. Si non, ne fais rien.

Cependant, cela signifie que l'image sera téléchargée dans tous les cas. Vous n'aurez tout simplement pas la mauvaise image sur la vue.

EDIT: D'abord, passez le ImageView au constructeur de la tâche:

new LoadImage(imageView).execute()

Enregistrez ensuite une référence au chemin ImageView et image dans le constructeur LoadImage. Il est important de sauvegarder le chemin dans le constructeur et non dans doInBackground afin d'éviter tout problème de multi-threading. Ensuite, à onPostExecute, nous vérifions le chemin actuel.

class LoadImage extends AsyncTask<Object, Void, Bitmap>{

        private ImageView imv;
        private String path;

        public LoadImage(ImageView imv) {
             this.imv = imv;
             this.path = imv.getTag().toString();
        }

    @Override
    protected Bitmap doInBackground(Object... params) {
        Bitmap bitmap = null;
        File file = new File( 
                Environment.getExternalStorageDirectory().getAbsolutePath() + path);

        if(file.exists()){
            bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
        }

        return bitmap;
    }
    @Override
    protected void onPostExecute(Bitmap result) {
        if (!imv.getTag().toString().equals(path)) {
               /* The path is not same. This means that this
                  image view is handled by some other async task. 
                  We don't do anything and return. */
               return;
        }

        if(result != null && imv != null){
            imv.setVisibility(View.VISIBLE);
            imv.setImageBitmap(result);
        }else{
            imv.setVisibility(View.GONE);
        }
    }

}
41
shabyasachi

Ce blog Développeurs Android vous donnera un projet de référence complet à cet effet avec la mise en cache. Il suffit de remplacer le code d’accès HTTP par les lectures de fichiers de la carte SD.

8
Jeff Axelrod

J'espère que cela aide… .. Après de nombreuses recherches, j'ai cette solution de travail.

public class CustomAdapter extends ArrayAdapter<String>{
    /*
    public CustomAdapter(Context context , String[] video) {
        super(context,R.layout.custom_row, video);
    }
*/

private final Activity context;
private final String[] video;

static class ViewHolder {
    public TextView videoTitle;
    public ImageView videoThumbnail;
    public int position;
    public String path;
}

public CustomAdapter(Activity context, String[] video) {
    super(context, R.layout.custom_row, video);
    this.context = context;
    this.video = video;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {

    LayoutInflater videoInflator = LayoutInflater.from(getContext());
    View customView = videoInflator.inflate(R.layout.custom_row, parent, false);
        ViewHolder viewHolder = new ViewHolder();
        viewHolder.position = position;
        viewHolder.path = video[position];
        viewHolder.videoTitle = (TextView) customView.findViewById(R.id.videoTitle);
        viewHolder.videoThumbnail = (ImageView) customView.findViewById(R.id.videoThumbnail);
        //rowView.setTag(viewHolder);
    //}
    customView.setTag(viewHolder);


    final String videoItem = video[position];
    int index=videoItem.lastIndexOf('/');
    String lastString=(videoItem.substring(index +1));
    index = lastString.indexOf(".mp4");
    lastString=(lastString.substring(0,index));
    viewHolder.videoTitle.setText(lastString);

    new AsyncTask<ViewHolder, Void, Bitmap>() {
        private ViewHolder v;

        @Override
        protected Bitmap doInBackground(ViewHolder... params) {
            v = params[0];
            Bitmap thumb = ThumbnailUtils.createVideoThumbnail(videoItem, MediaStore.Images.Thumbnails.MINI_KIND);
            return thumb;
        }

        @Override
        protected void onPostExecute(Bitmap result) {
            super.onPostExecute(result);
            if (v.position == position) {
                // If this item hasn't been recycled already, hide the
                // progress and set and show the image
                v.videoThumbnail.setImageBitmap(result);
            }
        }
    }.execute(viewHolder);
    return customView;
}

}

2
Amit

Peut-être devriez-vous essayer:

view = mInflater.inflate(mResource,parent,null);

Consultez ce blog, il explique le problème similaire:

http://www.doubleencore.com/2013/05/layout-inflation-as-intended/

Hey j'ai trouvé la solution à ce problème, utilisez simplement la fonction suivante à la place de votre fonction

 @Override
protected void onPostExecute(Bitmap result) {

    if (!imv.getTag().toString().equals(rec_id)) {
           return;
    }

    if(result != null && imv != null){
        int index = id.indexOf(imv.getTag().toString());
        if(list.getFirstVisiblePosition()<=index && index<=list.getLastVisiblePosition())
        {
            imv.setVisibility(View.VISIBLE);
            imv.setImageBitmap(result);
        }
    }else{
        imv.setImageBitmap(icon);
        imv.setVisibility(View.GONE);
    }
}

Ici la liste est l'objet de listview. Il vous suffit de transmettre votre objet vue liste à votre adaptateur et de coller cette fonction à la place de votre fonction onPostExecute.

0
Sandeep

Ce que je ferais (sauf si vous avez des milliers d’images): 1. créer une structure de données - une classe simple contenant un nom de chaîne à afficher et un bitmap 2. créez un adaptateur pour cela 3. Dans la méthode getView, affectez le bon bitmap à la bonne ImageView.

Dans votre cas, cependant, vous pouvez créer une structure de données similaire mais en tenant non pas un bitmap, mais un AsyncTask. Quoi qu’il en soit, vous devez lier l’asynctask à la chaîne en un seul élément. Un tableau (ou liste d'arborescence) de ces éléments sera envoyé à votre adaptateur. Affiché sera une imageview et une textview.

AsyncTask peut être annulé avec cancel ().

0
Yar