7

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.

Clive Jefferies
  • 1,138
  • 14
  • 26
  • please share the ImageDownloader code from your MainActivity – kandroidj Apr 10 '15 at 15:19
  • Hi, I cannot really share it as it is large. Basically it fetches the image and then sets the of the drawable once it has got it from memory or remotely. It is based on the BitmapFun sample. It works fine anywhere else I use images in the application. I think the problem is the adapter itself, rather than the image fetching. – Clive Jefferies Apr 10 '15 at 15:26
  • No big deal, i think your problem is as suggested below, your calling to download a new image, but your setting your bitmap to null for your image view. you should download this everytime you call to onBindViewHolder, if its already downloaded – kandroidj Apr 10 '15 at 15:29
  • Thanks, unfortunately it makes no difference if that is in or not. The problem is the the onBindViewHolder method for that cell is not called. As a result the image is not set. – Clive Jefferies Apr 10 '15 at 15:37
  • There is most likely something wrong with the image loader. Implementing something like this can be tricky. I suggest you use `Picasso`. – Xaver Kapeller Apr 10 '15 at 17:37
  • use piccaso library to load image – Aditi Sep 07 '16 at 06:15
  • https://stackoverflow.com/a/46142489/7569010 - This helped me sort out the same problem – Manoj J Aug 02 '21 at 13:52

3 Answers3

2

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 ...

AndreiBogdan
  • 10,858
  • 13
  • 58
  • 106
2

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.

Nongthonbam Tonthoi
  • 12,667
  • 7
  • 37
  • 64
Clive Jefferies
  • 1,138
  • 14
  • 26
1

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>