web-dev-qa-db-fra.com

RecyclerView: chargement d'image asynchrone

J'utilise RecyclerView pour afficher une liste contenant un imageView. Pour rendre l'interface plus fluide, je charge des miniatures de 58dp enregistrées sur la carte SD dans ces images avec un asyncTask.

Le problème est qu'une fois qu'un childView arrive en affichage visuel, une ancienne image provenant d'une autre donnée est réutilisée puis remplacée une fois le AsyncTask terminé. Je peux arrêter le brassage en définissant le bitmap imageView sur null dans onPreExecute.

Existe-t-il un moyen de vraiment réutiliser les anciennes images ou dois-je vraiment charger les images de la carte SD chaque fois qu'un nouveau View entre en place? Cela rend la vue assez moche car il y a d'abord des images erronées ou l'image est d'un blanc uni.

21
Paul Woitaschek

En raison de la réutilisation des vues, vous récupérerez les vues avec du contenu déjà dessus, c'était aussi un problème sur ListViews si vous utilisiez le modèle ViewHolder, ce que vous devriez.

Il y a deux solutions ici, la bonne pratique et le mauvais hack:

  • Dans la bonne pratique, vous définissez votre ImageView pour ne rien afficher au début de bindViewHolder(VH holder, int position) en utilisant setDrawable(null) ou similaire.

  • Dans le mauvais hack, vous ne recyclez/réutilisez pas les vues, n'appliquez pas le modèle ViewHolder et vous le gonflez à chaque fois, mais cela n'est autorisé que dans ListView et d'autres anciens composants.

32
MLProgrammer-CiM

Vous devriez vérifier le chargeur d'image universel . Il a un cache mémoire, un cache disque et il charge vos images de manière asynchrone, donc ne bloque pas l'interface utilisateur. Vous pouvez définir l'image par défaut et/ou ne pas avoir récupéré l'image, etc. Il peut échantillonner votre image pour réduire l'empreinte mémoire du bitmap. Je vous recommande vraiment de l'utiliser pour des images.

Ne désactivez pas recyclable pour votre cas car il est inutile. Les images doivent être recyclées parce que leurs éléments de dessin bitmap génèrent une surcharge de mémoire très élevée s'ils ne sont pas correctement échantillonnés.

Exemple d'utilisation dans RecyclerViewAdapter:

@Override
public void onBindViewHolder(CustomViewHolder viewHolder, int position) {
    String imageUri = "";//local or remote image uri address
    //viewHolder.imgView: reference to your imageview
    //before you call the displayImage you have to 
    //initialize imageloader in anywhere in your code for once.   
    //(Generally done in the Application class extender.)
    ImageLoader.getInstance().displayImage(imageUri, viewHolder.imgView);
}

edit: De nos jours, je considère Glide comme ma principale bibliothèque de chargement et de mise en cache d'images. Vous pouvez l'utiliser comme ceci:

Glide.with(context)
    .load(imageUri)
    .placeholder(R.drawable.myplaceholder)
    .into(imageView);
8
Gunhan

Vous devez annuler l'ancienne demande avant de commencer une nouvelle, mais quelle que soit l'annulation, vous pouvez toujours afficher la mauvaise image si les deux images sont chargées plus ou moins en même temps sur le même conteneur/support de vue qui a été recyclé (cela se produit facilement avec rapide défilement et petites images).

La solution est de:

  1. Stockez un identifiant unique dans le support de vue pendant onBindViewHolder (cela se produit de manière synchrone, donc si VH est recyclé, il sera remplacé)
  2. Chargez ensuite l'image de manière asynchrone (avec AsynchTask, RxJava, etc.) et passez cet identifiant unique dans l'appel asynchrone pour référence
  3. Enfin, dans la méthode chargée d'image pour le post-traitement (onPostExecute pour AsyncTasks), vérifiez que l'ID transmis dans la demande asynchrone est le même que l'ID actuel présent dans le View Holder.

Exemple de chargement d'icônes d'applications en arrière-plan avec RxJava:

 public void loadIcon(final ImageView appIconView, final ApplicationInfo appInfo, final String uniqueAppID) {
    Single.fromCallable(() -> {
            return appIconView.getContext().getPackageManager().getApplicationIcon(appInfo);
        })
          .subscribeOn(Schedulers.computation())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe( drawable -> {
                 if (uniqueAppID.equals(mUniqueAppID)) { // Show always the correct app icon
                    appIconView.setImageDrawable(drawable);
                 }
             }
         );
}

Ici mUniqueAppID est un champ du support de vue modifié par onBindViewHolder

5
Sebas LG

vous devez annuler l'ancienne demande dans la méthode "onBindViewHolder":

try{
        ((SpecialOfferViewHolder)viewHolder).imageContainer.cancelRequest();
}catch(Exception e) {

}

n'oubliez pas d'enregistrer le conteneur d'images dans le viewHolder:

public void onResponse(ImageContainer response, boolean arg1) {
                ((SpecialOfferViewHolder)viewHolder).imageContainer=response;

}

J'ajouterais à la bonne pratique:

Dans la bonne pratique, vous définissez votre ImageView pour ne rien afficher au début de bindViewHolder (support VH, position int) à l'aide de setDrawable (null) ou similaire.

non pas pour afficher rien, mais pour afficher une image de chargeur afin de donner à l'utilisateur un retour d'information sur le traitement en cours dans cette vue et dans un petit temps, il verra les résultats. Voir une vue vierge n'est pas une bonne pratique, vous devez donner votre avis à l'utilisateur.

0
Dieguinho