RecyclerView adapter showing wrong images

自古美人都是妖i 提交于 2020-11-30 09:19:42

问题


I have a RecyclerView adapter that looks like this:

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {

    private static Context context;
    private List<Message> mDataset;

    public RecyclerAdapter(Context context, List<Message> myDataset) {
        this.context = context;
        this.mDataset = myDataset;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener, View.OnClickListener {
        public TextView title;
        public LinearLayout placeholder;

        public ViewHolder(View view) {
            super(view);
            view.setOnCreateContextMenuListener(this);

            title = (TextView) view.findViewById(R.id.title);
            placeholder = (LinearLayout) view.findViewById(R.id.placeholder);
        }
    }

    @Override
    public RecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_layout, parent, false);
        ViewHolder vh = new ViewHolder((LinearLayout) view);

        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Message item = mDataset.get(position);

        holder.title.setText(item.getTitle());

        int numImages = item.getImages().size();

        if (numImages > 0) {
            View test = LayoutInflater.from(holder.placeholder.getContext()).inflate(R.layout.images, holder.placeholder, false);
            ImageView image = (ImageView) test.findViewById(R.id.image);
            Glide.with(context)
                .load("http://www.website.com/test.png")
                .fitCenter()
                .into(image);
            holder.placeholder.addView(test);
        }
    }

    @Override
    public int getItemCount() {
        return mDataset.size();
    }

}

However, some of the items in the RecyclerView are showing images when they shouldn't be. How can I stop this from happening?

I do the check if (numImages > 0) { in onBindViewHolder(), but that's still not stopping it from showing images for items that shouldn't have images.


回答1:


You should set imageView.setImageDrawable (null) In onBindViewHolder() before setting the image using glide.

Setting image drawable to null fix the issue.

Hope it helps!




回答2:


The problem is in onBindViewHolder, here:

    if (numImages > 0) {
        View test = LayoutInflater.from(holder.placeholder.getContext()).inflate(R.layout.images, holder.placeholder, false);
        ImageView image = (ImageView) test.findViewById(R.id.image);
        Glide.with(context)
            .load("http://www.website.com/test.png")
            .fitCenter()
            .into(image);
        holder.placeholder.addView(test);
    }

If numImages is equal to 0, you're simply allowing the previously started load into the view you're reusing to continue. When it finishes, it will still load the old image into your view. To prevent this, tell Glide to cancel the previous load by calling clear:

   if (numImages > 0) {
        View test = LayoutInflater.from(holder.placeholder.getContext()).inflate(R.layout.images, holder.placeholder, false);
        ImageView image = (ImageView) test.findViewById(R.id.image);
        Glide.with(context)
            .load("http://www.website.com/test.png")
            .fitCenter()
            .into(image);
        holder.placeholder.addView(test);
    } else {
        Glide.clear(image);
    }

When you call into(), Glide handles canceling the old load for you. If you're not going to call into(), you must call clear() yourself.

Every call to onBindViewHolder must include either a load() call or a clear() call.




回答3:


I also had issues with RecyclerView showing wrong images. This happens because RecyclerView is not inflating view for every new list item: instead list items are being recycled.

By recycling views we can ruffly understand cloning views. A cloned view might have an image set from the previous interaction.

This is especially fair if your are using Picasso, Glide, or some other lib for async loading. These libs hold reference to an ImageView, and set an image on that refference when image is loaded.

By the time the image gets loaded, the item view might have gotten cloned, and the image is going to be set to the wrong clone.

To make a long story short, I solved this problem by restricting RecyclerView from cloning my item views:

setIsRecyclable(false)in ViewHolder constructor.

Now RecyclerView is working a bit slower, but at least the images are set right.

Or else cansel loading image in onViewRecycled(ViewHolder holde)




回答4:


The issue here is that, as you are working with views that are going to be recycled, you'll need to handle all the possible scenarios at the time your binding your view.

For example, if you're adding the ImageView to the LinearLayout on position 0 of the data source, then, if position 4 doesn't met the condition, its view will most likely have the ImageView added when binding position 0.

You can add the content of R.layout.images content inside your

R.layout.message_layout layout's R.id.placeholder and showing/hiding the placeholder depending on the case.

So, your onBindViewHolder method would be something like:

@Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Message item = mDataset.get(position);

        holder.title.setText(item.getTitle());

        int numImages = item.getImages().size();

        if (numImages > 0) {
            holder.placeholder.setVisivility(View.VISIBLE);
            ImageView image = (ImageView)holder.placeholder.findViewById(R.id.image);
            Glide.with(context)
                .load("http://www.website.com/test.png")
                .fitCenter()
                .into(image);
        }else{
           holder.placeholder.setVisibility(View.INVISIBLE);
        }
    }



回答5:


Sometimes when using RecyclerView, a View may be re-used and retain the size from a previous position that will be changed for the current position. To handle those cases, you can create a new [ViewTarget and pass in true for waitForLayout]:

@Override
public void onBindViewHolder(VH holder, int position) {
  Glide.with(fragment)
    .load(urls.get(position))
    .into(new DrawableImageViewTarget(holder.imageView,/*waitForLayout=*/ true));

https://bumptech.github.io/glide/doc/targets.html




回答6:


This works for me in onBindViewHolder!

if(!m.getPicture().isEmpty())
{
    holder.setIsRecyclable(false);
    Picasso.with(holder.profile_pic.getContext()).load(m.getPicture()).placeholder(R.mipmap.ic_launcher_round).into(holder.profile_pic);
    Animation fadeOut = new AlphaAnimation(0, 1);
    fadeOut.setInterpolator(new AccelerateInterpolator());
    fadeOut.setDuration(1000);
    holder.profile_pic.startAnimation(fadeOut);
}
else
{
    holder.setIsRecyclable(true);
}



回答7:


I Had the same issue and i fixed it like this:

GOAL : onViewAttachedToWindow

@Override
public void onViewAttachedToWindow(Holder holder) {
    super.onViewAttachedToWindow(holder);
    StructAllItems sfi = mArrayList.get(position);   
    if (!sfi.getPicHayatParking().isEmpty()) {
        holder.viewFliperMelk.addSlider(new TextSliderView(mContext.getApplicationContext()).image(T.GET_MELK_IMAGE + '/' + sfi.getPicHayatParking() + ".jpg").setScaleType(BaseSliderView.ScaleType.CenterCrop));
    }
    if (!sfi.getPicSleepRoom().isEmpty()) {
        holder.viewFliperMelk.addSlider(new TextSliderView(mContext.getApplicationContext()).image(T.GET_MELK_IMAGE + '/' + sfi.getPicSleepRoom() + ".jpg").setScaleType(BaseSliderView.ScaleType.CenterCrop));
    }
    if (!sfi.getPicSalonPazirayi().isEmpty()) {
        holder.viewFliperMelk.addSlider(new TextSliderView(mContext.getApplicationContext()).image(T.GET_MELK_IMAGE + '/' + sfi.getPicSalonPazirayi() + ".jpg").setScaleType(BaseSliderView.ScaleType.CenterCrop));
    }
    if (!sfi.getPicNamayeStruct().isEmpty()) {
        holder.viewFliperMelk.addSlider(new TextSliderView(mContext.getApplicationContext()).image(T.GET_MELK_IMAGE + '/' + sfi.getPicNamayeStruct() + ".jpg").setScaleType(BaseSliderView.ScaleType.CenterCrop));
    }
}



回答8:


I also had the same problem and ended with below solution and it working fine for me..

Have your hands on this solution might be work for you too (Put below code in your adapter class)-

If you are using Kotlin -

override fun getItemId(position: Int): Long {
        return position.toLong()
    }

    override fun getItemViewType(position: Int): Int {
        return position
    }

If you are using JAVA -

 @Override
    public long getItemId(int position) {
        return position;
    }

@Override
    public int getItemViewType(int position) {
        return position;
    }



回答9:


I had a similar issue when getting pictures from the photo gallery and putting them in a recyclerview with GridLayoutManager(never had the issue with Glide). So in the adapter onBindViewHolder use a HashMap or SparseIntArray to put the current hashcode(this is the common thing that the recycled views have in common) and adapter position inside it. Then call your background task and then once it's done and before you set the image, check to see if the hashcode key - which will always have the current adapter position as the value - still has the same value (adapter position) as when you first called the background task.

(Global variable)
   private SparseIntArray hashMap = new SparseIntArray();

onBindViewHolder(ViewHolder holder, int position){

   holder.imageView.setImageResource(R.drawable.grey_square);
   hashMap.put(holder.hashCode(), position);
   yourBackgroundTask(ViewHolder holder, int position);
}

yourBackGroundTask(ViewHolder holder, int holderPosition){

   do some stuff in the background.....
      *if you want to stop to image from downloading / or in my case 
       fetching the image from MediaStore then do - 
         if(hashMap.get(holder.hashCode())!=(holderPos)){
                 return null;
             } 
         - in the background task, before the call to get the 
           image

   onPostExecute{
      if(hashMap.get(holder.hashCode())==(holderPosition)){
         holder.imageView.setImageBitmap(result);
      }
   }
}



回答10:


So i am just providing an extension to this answer since there is not much space to leave it as comment.

After trying out like mentioned in one of above solutions i found out that, the real issue can still be addressed even if you are using a static resource(is not being downloaded and is available locally)

So basically on onBindViewHolder event i just converted the resource to drawable and added it like below :

imageView.setImageDrawable(ContextCompat.getDrawable(context,R.drawable.album_art_unknown));

this way you wont have an empty space on the view while glide/async downloader is loading the actual image from network.

plus looking at that being reloaded every time i also added below code while calling the recycler adapter class;

recyclerView.setItemViewCacheSize(10);
recyclerView.setDrawingCacheEnabled(true);

so by using above way you wont need to set setIsRecyclable(false) which is degrading if you have larger datasets.

By doing this i you will have a flicker free loading of recyclerview of course except for the initial loads.



来源:https://stackoverflow.com/questions/33835867/recyclerview-adapter-showing-wrong-images

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!