web-dev-qa-db-fra.com

Savoir quand une vue dans un ListView s'est éteinte?

J'ai googlé ça mais je ne trouve pas de réponse, alors voilà ...

J'ai un ListView qui affiche du texte et une image. L'adaptateur sous-jacent recycle les vues pour des raisons de performances (conformément à l'approche recommandée) et je charge les images à l'aide d'une AsynchTask conformément à la recommandation trouvée sur la page bitmap du site des développeurs Android .

Tout fonctionne parfaitement et en douceur, mais j'ai un problème. Lorsque la vue de l'adaptateur est recyclée, il y a toujours une référence à l'ancienne image (ImageView). Si l'utilisateur fait défiler lentement, la tâche AsynchTask dispose de suffisamment de temps pour charger la nouvelle image et l'afficher. Il n'y a donc aucun rechargement visible de la nouvelle image pour l'utilisateur.

Cependant, si l'utilisateur fait défiler très rapidement, le retard dans le chargement de l'image signifie qu'il voit l'ancienne image (chargée lors de l'utilisation de la Vue par un autre élément) avant d'être remplacée par la nouvelle image.

Ma question est donc la suivante: comment puis-je détecter le moment où une vue n'est plus visible à l'écran pour pouvoir ensuite supprimer l'image? Cela signifierait que l'utilisateur verra un élément de vue liste vide qui sera éventuellement chargé avec l'image appropriée, ce qui lui donnera un meilleur aspect.

Merci d'avance. Voici le code de l'adaptateur pour la vue en liste.

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

    // if this is not a recycled view then create a new view for the data...
    if (view == null)
    {
        view = this.inflater.inflate(R.layout.target_list_view_layout, null, true);

        viewHolder = new ListViewHolder();

        viewHolder.manufacturer = (TextView) view.findViewById(R.id.manufacturer);
        viewHolder.targetName = (TextView) view.findViewById(R.id.targetName);
        viewHolder.targetThumbnail = (ImageView) view.findViewById(R.id.targetThumbnail);

        view.setTag(viewHolder);
    } else
    {
        viewHolder = (ListViewHolder) convertView.getTag();
    }

    TargetDescriptor targetDescriptor = this.selectedTargets.get(position);

    viewHolder.manufacturer.setText(targetDescriptor.manufacturer);
    viewHolder.targetName.setText(targetDescriptor.targetName);

    // At this point I pass the image view reference to my background task to load the image
    LoadImageViewAsynchTask loadImageTask = new LoadImageViewAsynchTask(viewHolder.targetThumbnail, targetDescriptor);
    loadImageTask.execute(new Integer[]
    { 64, 64 });

    return view;
}

EDIT: Ceux qui utilisent l'application Android eBay peuvent voir l'effet que je recherche si cela aide.

25
Justin Phillips

Je n'arrive pas à croire à quel point j'ai été stupide, c'est une solution facile à résoudre!

Fondamentalement, dans mon adaptateur, si la vue est en cours de recyclage, je dois simplement annuler le bitmap ImageView avant que la vue ne soit réutilisée. Cela signifie que l'image dans l'élément de vue liste est vide avant le chargement de l'image suivante. Je n'ai plus le problème de l'ancienne image étant là pendant que la nouvelle image est chargée en arrière-plan.

J'ai également apporté une modification à l'adaptateur qui annule les tâches asynchrones en cours liées à la vue susceptibles de charger une image précédente dont vous n'avez plus besoin. Cela était très courant lorsque je parcourais très rapidement la vue Liste, avec souvent deux ou plusieurs chargements d’images sauvegardant, de sorte que l’image changerait plusieurs fois avant de choisir la bonne image.

Voici le nouveau code, avec les modifications commentées afin que vous puissiez voir ce qui se passe:

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

    // if this is not a recycled view then create a new view for the data...
    if (convertView == null)
    {
        convertView = this.inflater.inflate(R.layout.target_list_view_layout, null, true);

        viewHolder = new ListViewHolder();

        viewHolder.manufacturer = (TextView) convertView.findViewById(R.id.manufacturer);
        viewHolder.targetName = (TextView) convertView.findViewById(R.id.targetName);
        viewHolder.targetThumbnail = (ImageView) convertView.findViewById(R.id.targetThumbnail);

        convertView.setTag(viewHolder);
    } else
    {
        viewHolder = (ListViewHolder) convertView.getTag();

        // Cancel the previous attempt to load an image as this is going to be superceded by the next image
        viewHolder.loadImageViewAsynchTask.cancel(true);

        // Clear down the old image so when this view is displayed, the user does not see the old image before the
        // new image has a chance to load in the background
        viewHolder.targetThumbnail.setImageBitmap(null);
    }

    TargetDescriptor targetDescriptor = this.selectedTargets.get(position);

    viewHolder.manufacturer.setText(targetDescriptor.manufacturer);
    viewHolder.targetName.setText(targetDescriptor.targetName);

    LoadImageViewAsynchTask loadImageViewAsynchTask = new LoadImageViewAsynchTask(viewHolder.targetThumbnail);
    loadImageViewAsynchTask.setTargetDescriptor(targetDescriptor);
    loadImageViewAsynchTask.execute(new Integer[]
    { 64, 64 });

    // Keep a reference to the task so we can cancel it if the view is recycled next time round to prevent
    // un-neccessary image loads that are out of date
    viewHolder.loadImageViewAsynchTask = loadImageViewAsynchTask;

    return convertView;
}
3
Justin Phillips

Il existe en fait un moyen plus simple de faire cela, vous pouvez utiliser un écouteur:

mListView.setRecyclerListener(new AbsListView.RecyclerListener() {
@Override
    public void onMovedToScrapHeap(View view) {
  }
});
28
oznus

Si vous utilisez un LruCache pour les bitmaps et que chaque vue de la vue liste conserve la clé de l'élément LruCache, vous pourrez mieux le contrôler. Ensuite, dans getView, vous devez d'abord obtenir le bitmap du LruCache et le télécharger à nouveau. si ce n'est pas là.

En outre, vous pouvez définir setOnScrollListener () sur ListView et utiliser son paramètre visibleItemCount pour ajuster la taille de LruCache lors du premier défilement de l'utilisateur.

0
Rick Falck