Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView disappearing images

What I am trying to create is a horizontal scrolling image gallery. I have a RecyclerView (support 22.0.0). The problem I am having is that when I scroll to the end and then scroll back, usually one image will be missing sometimes two. Strangely when I keep swiping back and forth, a different image could be missing. Here is the layout for the item:

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="160dp">

<ImageView
    android:id="@+id/product_variation_image"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="centerCrop"
    android:layout_gravity="center"/>

Here is the Adaper:

public class TestAdapter extends RecyclerView.Adapter<TestAdapter.ViewHolder> {
private String[] mDataset;

public static class ViewHolder extends RecyclerView.ViewHolder {

    public ImageView mImageView;
    public ViewHolder(View v) {
        super(v);
        mImageView = (ImageView) v.findViewById(R.id.product_variation_image);
    }
}

public TestAdapter(String[] myDataset) {
    mDataset = myDataset;
}

@Override
public TestAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                               int viewType) {
    // create a new view
    View v = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.variaton_list_item, parent, false);
    ViewHolder vh = new ViewHolder(v);
    return vh;
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {

    holder.mImageView.setImageDrawable(null);
    String url = mDataset[position];
    Log.i("TEST", "position = " + position);
    ((MainActivity)MainActivity.getInstance()).imageDownloader.download(url, holder.mImageView);
}

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

The download method fetches the image, from a URL or gets it from the memory if it has been cached. This works fine in all other layouts e.g. ListView or GridView. Here is the code I use to set it up in the Fragment:

    final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
    layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
    mRecyclerView.setLayoutManager(layoutManager);

This is in the onCreateView method. When I get the urls I populate them and set the adapter using:

  myDataset[i] = imageURL; // for each image       
  mAdapter = new TestAdapter(myDataset);
  mRecyclerView.setAdapter(mAdapter);

The interesting thing is the line in the onBindViewHolder method in the adapter, where I log the position. What I have found is that cells where the image is not shown is that this method is not being called. It is like it is skipping that cell for some reason. Even stranger, if I hold a cell and keep swiping from left to right, if a cell goes off screen and then comes back in, its image as gone as again the onBindViewHolder method is not called.

like image 592
Clive Jefferies Avatar asked Apr 10 '15 15:04

Clive Jefferies


3 Answers

Would it be possible to test something out? Could you use this library to load the images from the URLs ? http://square.github.io/picasso/ It caches everything and it handles everything in an async manner.

Use it something like ...

@Override
public void onBindViewHolder(ViewHolder holder, int position) {

    Picasso.with(mImageView.getContext()).cancelRequest(holder.mImageView);

    String url = mDataset[position];
    Picasso.with(mImageView.getContext()).load(url).placeholder(R.drawable.placeholder).into(holder.mImageView);

}

... and see if it still doesn't display some images. If it does, then at least you'll be 100% sure the problem is not in your downloading mechanism (which I think it might be).

If you're using Android Studio then just add the dependency compile 'com.squareup.picasso:picasso:2.5.2', if not you can add the library you find at the above link.

It's worth a try ...

like image 179
AndreiBogdan Avatar answered Nov 13 '22 12:11

AndreiBogdan


The one class that I did not think would matter was the one that was causing the issue. I am not sure what the reason is, but it resides in a custom ImageView class that I am using for recycling that I got from the BitmapFun sample.

    public class RecyclingImageView extends ImageView {

    public RecyclingImageView(Context context) {
        super(context);
    }

    public RecyclingImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * @see android.widget.ImageView#onAttachedToWindow()
     */
    @Override
    protected void onAttachedToWindow() {}

    /**
     * @see android.widget.ImageView#onDetachedFromWindow()
     */
    @Override
    protected void onDetachedFromWindow() {
        // This has been detached from Window, so clear the drawable

        setImageDrawable(null); 

        super.onDetachedFromWindow();
    }

    /**
     * @see android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)
     */
    @Override
    public void setImageDrawable(Drawable drawable) {
        // Keep hold of previous Drawable
        final Drawable previousDrawable = getDrawable();

        // Call super to set new Drawable
        super.setImageDrawable(drawable);

        // Notify new Drawable that it is being displayed
        notifyDrawable(drawable, true);

        // Notify old Drawable so it is no longer being displayed
        notifyDrawable(previousDrawable, false);
    }

    /**
     * Notifies the drawable that it's displayed state has changed.
     *
     * @param drawable
     * @param isDisplayed
     */
    private static void notifyDrawable(Drawable drawable, final boolean isDisplayed) {
        if (drawable instanceof RecyclingBitmapDrawable) {
            // The drawable is a CountingBitmapDrawable, so notify it
            ((RecyclingBitmapDrawable) drawable).setIsDisplayed(isDisplayed);
        } else if (drawable instanceof LayerDrawable) {
            // The drawable is a LayerDrawable, so recurse on each layer
            LayerDrawable layerDrawable = (LayerDrawable) drawable;
            for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) {
                notifyDrawable(layerDrawable.getDrawable(i), isDisplayed);
            }
        }
    }

}

When I replace this with a normal ImageView, I no longer get the problem.

like image 2
Clive Jefferies Avatar answered Nov 13 '22 12:11

Clive Jefferies


We can fix the issue by extends LinearLayoutManager and ImageView.

1. Creats a PrecachingLinearLayoutManager

public class PrecachingLinearLayoutManager extends LinearLayoutManager {

    private static final int DEFAULT_EXTRA_LAYOUT_SPACE = 600;

    private int extraLayoutSpace = -1;

    @SuppressWarnings("unused")
    private Context mContext;

    public PrecachingLinearLayoutManager(Context context) {
        super(context);
        this.mContext = context;
    }

    public PrecachingLinearLayoutManager(Context context, int extraLayoutSpace) {
        super(context);
        this.mContext = context;
        this.extraLayoutSpace = extraLayoutSpace;
    }

    public PrecachingLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
        this.mContext = context;
    }

    public void setExtraLayoutSpace(int extraLayoutSpace) {
        this.extraLayoutSpace = extraLayoutSpace;
    }

    @Override
    protected int getExtraLayoutSpace(RecyclerView.State state) {
        if (extraLayoutSpace > 0) {
            return (extraLayoutSpace);
        }
        return (DEFAULT_EXTRA_LAYOUT_SPACE);
    }
}

2. Use PrecachingLinearLayoutManager to replace LinearLayoutManager

    DisplayMetrics displayMetrics = new DisplayMetrics();
    getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
    PrecachingLinearLayoutManager layout = new PrecachingLinearLayoutManager(getActivity());
    layout.setExtraLayoutSpace(displayMetrics.heightPixels);
    recyclerview.setLayoutManager(layout);

3. Creats a RecycleImageView

private Object tag = null;

@Override
protected void onAttachedToWindow() {
    Object tag = getTag();
    if (tag == null || !tag.equals(this.tag)) {
        // Will cause displayed bitmap wrapper to 
        // be 'free-able'
        setImageDrawable(null);
        this.tag = null;
        super.onDetachedFromWindow();
    }
    super.onAttachedToWindow();
}

@Override
protected void onDetachedFromWindow() {
    Object tag = getTag();
    if (tag != null) {
        this.tag = tag;
    } else {
        // Will cause displayed bitmap wrapper to 
        // be 'free-able'
        setImageDrawable(null);
        this.tag = null;
        super.onDetachedFromWindow();
    }
}

4. Use RecycleImageView to replace ImageView

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:extends="http://schemas.android.com/apk/res/com.yourdomain.yourpackage"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/viewgroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<com.yourdomain.yourpackage.RecycleImageView
    android:id="@+id/photo"
    android:layout_width="40dp"
    android:layout_height="40dp"
    extends:delayable="true"
    android:contentDescription="@string/nothing"
    android:src="@drawable/photo_placeholder" >
</com.yourdomain.yourpackage.RecycleImageView>
</LinearLayout>
like image 1
York GuangYu DENG Avatar answered Nov 13 '22 14:11

York GuangYu DENG