So i am using recycler view to show images in a grid and downloading images from url as bitmaps using volley library.
public void onBindViewHolder(final TrendingAdapter.ViewHolder viewHolder, int i) {
ImageRequest request = new ImageRequest(url, new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap bitmap) {
if (bitmap != null) {
viewHolder.getmImageView().setImageBitmap(bitmap);
}
}
}, 0, 0, null,
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
}
});
AppController.getInstance().addToRequestQueue(request);
}
The problem is when i scroll and skip one or more views before the image is downloaded and that view is recycled the image donwnload reqest is not canceled on those intermediate view resulting in a flash of that/those image(s) before the actual image is loaded in that view.
So i thought of canceling those intermediate image requests using tags but cannot figure out how as it results in canceling of request in other parallel views as well!
Also when i use volley NetworkImageView (that handels such image canceling by itself) gives perfect results. But i need to get bitmap of every image to pick colors from it so i cannot use NetworkImageView.
Q) How do i cancel all pending volley image requsts (except the one it should be loading and without effecting other parallel views) on a specific imageview inflated using recyclerview?
You should use the ImageLoader
class instead of directly adding a request to the RequestQueue
. That way, the get()
method, which is used to get images, will return an object of type ImageContainer
.
Save this ImageContainer
in the ViewHolder
, and when a view gets recycled, simply call the cancelRequest()
method on the recycled image to cancel the request if it hasn't been performed yet.
Take a look at the NetworkImageView
's code. It works in a similar way.
I solved the problem ! yaaeeye :D @Itai 's answer pointed me in the correct direction. I took a close look at NetworkImageView code and modified it according to my need. So i made a CustumNetworkImageView
public class CustomNetworkImageView extends ImageView {
// Added code block start
public interface ResponseObserver {
public void onError();
public void onSuccess();
}
private ResponseObserver mResponseObserver;
public void setmResponseObserver(ResponseObserver observer) {
mResponseObserver = observer;
}
private Bitmap bmp = null;
// Added code block end
private String mUrl;
private int mDefaultImageId;
private int mErrorImageId;
private ImageLoader mImageLoader;
public CustomNetworkImageView(Context context) {
this(context, null);
}
public CustomNetworkImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomNetworkImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setImageUrl(String url, ImageLoader imageLoader) {
mUrl = url;
mImageLoader = imageLoader;
loadImageIfNecessary();
}
public void setDefaultImageResId(int defaultImage) {
mDefaultImageId = defaultImage;
}
public void setErrorImageResId(int errorImage) {
mErrorImageId = errorImage;
// Added code block start
if (mResponseObserver != null) {
mResponseObserver.onError();
}
// Added code block end
}
// Added code block start
public Bitmap getBitmap() {
return bmp;
}
// Added code block end
private void loadImageIfNecessary() {
int width = getWidth();
int height = getHeight();
if (width == 0 && height == 0) {
return;
}
if (TextUtils.isEmpty(mUrl)) {
ImageContainer oldContainer = (ImageContainer) getTag();
if (oldContainer != null) {
oldContainer.cancelRequest();
setImageBitmap(null);
}
return;
}
ImageContainer oldContainer = (ImageContainer) getTag();
if (oldContainer != null && oldContainer.getRequestUrl() != null) {
if (oldContainer.getRequestUrl().equals(mUrl)) {
return;
} else {
oldContainer.cancelRequest();
setImageBitmap(null);
}
}
ImageContainer newContainer = mImageLoader.get(mUrl,
ImageLoader.getImageListener(this, mDefaultImageId, mErrorImageId));
setTag(newContainer);
final Bitmap bitmap = newContainer.getBitmap();
if (bitmap != null) {
setImageBitmap(bitmap);
// Added code block start
bmp = bitmap;
if (mResponseObserver != null) {
mResponseObserver.onSuccess();
}
// Added code block end
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
loadImageIfNecessary();
}
@Override
protected void onDetachedFromWindow() {
ImageContainer oldContainer = (ImageContainer) getTag();
if (oldContainer != null) {
oldContainer.cancelRequest();
setImageBitmap(null);
setTag(null);
}
super.onDetachedFromWindow();
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
invalidate();
}
}
Using CustomNetworkImageView :
viewHolder.getmImageView().setImageUrl(wallThumb.get(i), AppController.getInstance().getImageLoader());
viewHolder.getmImageView().setmResponseObserver(new CustomNetworkImageView.ResponseObserver() {
@Override
public void onError() {
}
@Override
public void onSuccess() {
// Image is loaded in ImageView.. Do Something..
Bitmap bitmap = viewHolder.getmImageView().getBitmap();
}
});
SetmResponseObserver listens for if bitmap is loaded in ImageView and onSuccess is called if task is completed.. I also made a fuction that returns the bitmap on the image view and can be used as
Bitmap bmp = viewHolde.getmImageView.getBitmap();
I am new to android so please feel free to comment and correct me if i have done anything wrong. Yaaaeeyeee happy dayyy
public class RecyclerViewPagerNetworkImageView extends NetworkImageView {
public RecyclerViewPagerNetworkImageView(Context context) {
super(context);
}
public RecyclerViewPagerNetworkImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RecyclerViewPagerNetworkImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onDetachedFromWindow() {
// delete
/*if (mImageContainer != null) {
// If the view was bound to an image request, cancel it and clear
// out the image from the view.
mImageContainer.cancelRequest();
setImageBitmap(null);
// also clear out the container so we can reload the image if necessary.
mImageContainer = null;
}
super.onDetachedFromWindow();*/
}
}
I faced the same problem. I couldn't use NetworkImageView
and image loader because I wanted to have a custom image request.
So I found another way to cancel in flight request. I simply passed the image request in ViewHolder
object from onBindViewHolder
callback. In ViewHolder
created a method setRequest(Request request)
.
In this method check if the request is not null then call request.cancel()
first otherwise just set the request into the holder.
public class ViewHolder extends RecyclerView.ViewHolder {
public Request request;
public ViewHolder(View view) {
super(view);
}
public void setRequest(Request request) {
if (this.request != null) {
this.request.cancel();
}
this.request = request;
}
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
ImageRequest imageRequest = new ImageRequest(.......);
holder.setRequest(request);
mRequestQueue.add(request);
}
来源:https://stackoverflow.com/questions/26591390/recycler-view-with-volley-image-request-cancel-request