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.
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 ...
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.
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With