Im using RecyclerView
to display a list containing an imageView
. To make the UI more fluently, I load 58dp thumbnails saved on sd card into these imageViews with an asyncTask
.
The problem is, that once a childView
comes in visual display, an old image from another data is being reused and then replaced once the AsyncTask
finishes. I can stop the shuffling by setting the imageView
bitmap to null in onPreExecute
.
Is there a way to really reuse old images or do I really have to load the images from sd-card each time a new View
comes in place? This makes view quite ugly because either there are wrong images first or the image is plain white.
Due to view reuse you'll fetch views with content already on them, this was a problem on ListViews
too if you were using the ViewHolder
pattern, which you should.
There are two solutions here, the good practice and the bad hack:
In the good practice you set your ImageView
to display nothing at the
beginning of bindViewHolder(VH holder, int position)
using
setDrawable(null)
or similar.
In the bad hack you wouldn't recycle/reuse views, not enforcing the ViewHolder
pattern, and you'd inflate it every time, but that's only allowed in ListView
and other old components.
You should check the universal image loader. It has memory cache, disk cache and it loads your images asynchronously so doesn't block the ui. You can set default image and/or failed to fetch image etc. It can sample down your image to decrease the memory footprint of the bitmap. I really recommend you to use it for images.
Do not disable recyclable for your case because it is pointless. Images must be recycled because their bitmap drawables generate very high memory overload if not properly sampled.
Sample usage in RecyclerViewAdapter:
@Override
public void onBindViewHolder(CustomViewHolder viewHolder, int position) {
String imageUri = "";//local or remote image uri address
//viewHolder.imgView: reference to your imageview
//before you call the displayImage you have to
//initialize imageloader in anywhere in your code for once.
//(Generally done in the Application class extender.)
ImageLoader.getInstance().displayImage(imageUri, viewHolder.imgView);
}
edit: Nowadays, I consider Glide as my main image loading and caching library. You can use it like this:
Glide.with(context)
.load(imageUri)
.placeholder(R.drawable.myplaceholder)
.into(imageView);
You should cancel the old request before starting a new one, but regardless of cancelling you can still show the wrong image if both images loaded more or less at the same time on the same container/view holder that has been recycled (happens easily with fast scroll and small images).
The solution is to:
Example loading icons from apps in background with RxJava:
public void loadIcon(final ImageView appIconView, final ApplicationInfo appInfo, final String uniqueAppID) {
Single.fromCallable(() -> {
return appIconView.getContext().getPackageManager().getApplicationIcon(appInfo);
})
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe( drawable -> {
if (uniqueAppID.equals(mUniqueAppID)) { // Show always the correct app icon
appIconView.setImageDrawable(drawable);
}
}
);
}
Here mUniqueAppID is a field of the view holder changed by onBindViewHolder
you must cancel old request in "onBindViewHolder" method:
try{
((SpecialOfferViewHolder)viewHolder).imageContainer.cancelRequest();
}catch(Exception e) {
}
remember to save image container in the viewHolder:
public void onResponse(ImageContainer response, boolean arg1) {
((SpecialOfferViewHolder)viewHolder).imageContainer=response;
}
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