Created
September 13, 2015 23:56
-
-
Save gitanuj/fde573fc04332752ea8f to your computer and use it in GitHub Desktop.
A MultiImageLoader for loading multiple images using Volley on Android. Implementation of MultiNetworkImageView included.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.ArrayList; | |
import java.util.List; | |
import android.widget.ImageView; | |
import com.android.volley.VolleyError; | |
import com.android.volley.toolbox.ImageLoader; | |
import com.android.volley.toolbox.ImageLoader.ImageContainer; | |
import com.android.volley.toolbox.ImageLoader.ImageListener; | |
public class MultiImageLoader { | |
private final ImageLoader mImageLoader; | |
public static MultiImageLoader wrap(ImageLoader imageLoader) { | |
return new MultiImageLoader(imageLoader); | |
} | |
private MultiImageLoader(ImageLoader imageLoader) { | |
mImageLoader = imageLoader; | |
} | |
public MultiImageContainer get(List<String> requestUrls, final ImageListener listener) { | |
return get(requestUrls, listener, 0, 0); | |
} | |
public MultiImageContainer get(List<String> requestUrls, ImageListener imageListener, int maxWidth, int maxHeight) { | |
return get(requestUrls, imageListener, maxWidth, maxHeight, ImageView.ScaleType.CENTER_INSIDE); | |
} | |
public MultiImageContainer get(final List<String> requestUrls, ImageListener imageListener, int maxWidth, | |
int maxHeight, ImageView.ScaleType scaleType) { | |
MultiImageContainer multiImageContainer = new MultiImageContainer(requestUrls); | |
loadNext(multiImageContainer, new ArrayList<>(requestUrls), imageListener, maxWidth, maxHeight, scaleType, true); | |
return multiImageContainer; | |
} | |
private void loadNext(final MultiImageContainer multiImageContainer, final List<String> requestUrls, | |
final ImageListener imageListener, final int maxWidth, final int maxHeight, | |
final ImageView.ScaleType scaleType, final boolean firstLoad) { | |
if (multiImageContainer.isCancelled || requestUrls.isEmpty()) { | |
return; | |
} | |
ImageContainer imageContainer = mImageLoader.get(requestUrls.remove(0), new ImageListener() { | |
@Override | |
public void onResponse(ImageContainer response, boolean isImmediate) { | |
if (response.getBitmap() != null || firstLoad) { | |
imageListener.onResponse(response, isImmediate); | |
} | |
if (response.getBitmap() != null) { | |
// Current loading complete. Lets load the next one. | |
loadNext(multiImageContainer, requestUrls, imageListener, maxWidth, maxHeight, scaleType, false); | |
} | |
} | |
@Override | |
public void onErrorResponse(VolleyError error) { | |
if (firstLoad) { | |
imageListener.onErrorResponse(error); | |
} | |
} | |
}, maxWidth, maxHeight, scaleType); | |
multiImageContainer.add(imageContainer); | |
} | |
public class MultiImageContainer { | |
private final List<String> mUrls; | |
private final List<ImageContainer> mImageContainers; | |
private boolean isCancelled; | |
public MultiImageContainer(List<String> urls) { | |
mUrls = urls; | |
mImageContainers = new ArrayList<>(); | |
} | |
public List<String> getRequestUrls() { | |
return mUrls; | |
} | |
private boolean add(ImageContainer imageContainer) { | |
return mImageContainers.add(imageContainer); | |
} | |
public void cancelRequests() { | |
isCancelled = true; | |
for (ImageContainer imageContainer : mImageContainers) { | |
imageContainer.cancelRequest(); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.List; | |
import android.content.Context; | |
import android.util.AttributeSet; | |
import android.view.ViewGroup; | |
import android.widget.ImageView; | |
import com.android.volley.VolleyError; | |
import com.android.volley.toolbox.ImageLoader; | |
public class MultiNetworkImageView extends ImageView { | |
/** The URLs of the network image to load */ | |
private List<String> mUrls; | |
/** | |
* Resource ID of the image to be used as a placeholder until the network | |
* image is loaded. | |
*/ | |
private int mDefaultImageId; | |
/** | |
* Resource ID of the image to be used if the network response fails. | |
*/ | |
private int mErrorImageId; | |
/** Local copy of the MultiImageLoader. */ | |
private MultiImageLoader mMultiImageLoader; | |
/** Current ImageContainer. (either in-flight or finished) */ | |
private MultiImageLoader.MultiImageContainer mMultiImageContainer; | |
private ImageLoader.ImageListener mImageListener; | |
public MultiNetworkImageView(Context context) { | |
this(context, null); | |
} | |
public MultiNetworkImageView(Context context, AttributeSet attrs) { | |
this(context, attrs, 0); | |
} | |
public MultiNetworkImageView(Context context, AttributeSet attrs, int defStyle) { | |
super(context, attrs, defStyle); | |
} | |
public void setImageUrls(List<String> urls, MultiImageLoader MultiImageLoader) { | |
mUrls = urls; | |
mMultiImageLoader = MultiImageLoader; | |
loadImageIfNecessary(false); | |
} | |
public void setListener(ImageLoader.ImageListener imageListener) { | |
mImageListener = imageListener; | |
} | |
public void setDefaultImageResId(int defaultImage) { | |
mDefaultImageId = defaultImage; | |
} | |
public void setErrorImageResId(int errorImage) { | |
mErrorImageId = errorImage; | |
} | |
void loadImageIfNecessary(final boolean isInLayoutPass) { | |
int width = getWidth(); | |
int height = getHeight(); | |
ScaleType scaleType = getScaleType(); | |
boolean wrapWidth = false, wrapHeight = false; | |
if (getLayoutParams() != null) { | |
wrapWidth = getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT; | |
wrapHeight = getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT; | |
} | |
// if the view's bounds aren't known yet, and this is not a | |
// wrap-content/wrap-content view, hold off on loading the image. | |
boolean isFullyWrapContent = wrapWidth && wrapHeight; | |
if (width == 0 && height == 0 && !isFullyWrapContent) { | |
return; | |
} | |
// if the URL to be loaded in this view is empty, cancel any old | |
// requests and clear the currently loaded image. | |
if (mUrls == null || mUrls.isEmpty()) { | |
if (mMultiImageContainer != null) { | |
mMultiImageContainer.cancelRequests(); | |
mMultiImageContainer = null; | |
} | |
setDefaultImageOrNull(); | |
return; | |
} | |
// if there was an old request in this view, check if it needs to be | |
// canceled. | |
if (mMultiImageContainer != null && mMultiImageContainer.getRequestUrls() != null) { | |
if (mMultiImageContainer.getRequestUrls().equals(mUrls)) { | |
// if the request is from the same URL, return. | |
return; | |
} else { | |
// if there is a pre-existing request, cancel it if it's | |
// fetching a different URL. | |
mMultiImageContainer.cancelRequests(); | |
setDefaultImageOrNull(); | |
} | |
} | |
// Calculate the max image width / height to use while ignoring | |
// WRAP_CONTENT dimens. | |
int maxWidth = wrapWidth ? 0 : width; | |
int maxHeight = wrapHeight ? 0 : height; | |
// The pre-existing content of this view didn't match the current URL. | |
// Load the new image from the network. | |
MultiImageLoader.MultiImageContainer newContainer = mMultiImageLoader.get(mUrls, | |
new ImageLoader.ImageListener() { | |
@Override | |
public void onErrorResponse(VolleyError error) { | |
if (mErrorImageId != 0) { | |
setImageResource(mErrorImageId); | |
} | |
onErrorResponseReceived(error); | |
} | |
@Override | |
public void onResponse(final ImageLoader.ImageContainer response, boolean isImmediate) { | |
/* | |
* If this was an immediate response that was delivered | |
* inside of a layout pass do not set the image | |
* immediately as it will trigger a requestLayout inside | |
* of a layout. Instead, defer setting the image by | |
* posting back to the main thread. | |
*/ | |
if (isImmediate && isInLayoutPass) { | |
post(new Runnable() { | |
@Override | |
public void run() { | |
onResponse(response, false); | |
} | |
}); | |
return; | |
} | |
if (response.getBitmap() != null) { | |
setImageBitmap(response.getBitmap()); | |
} else if (mDefaultImageId != 0) { | |
setImageResource(mDefaultImageId); | |
} | |
onResponseReceived(response, isImmediate); | |
} | |
}, maxWidth, maxHeight, scaleType); | |
// update the ImageContainer to be the new bitmap container. | |
mMultiImageContainer = newContainer; | |
} | |
private void onErrorResponseReceived(VolleyError error) { | |
if (mImageListener != null) { | |
mImageListener.onErrorResponse(error); | |
} | |
} | |
private void onResponseReceived(ImageLoader.ImageContainer response, boolean isImmediate) { | |
if (mImageListener != null) { | |
mImageListener.onResponse(response, isImmediate); | |
} | |
} | |
private void setDefaultImageOrNull() { | |
if (mDefaultImageId != 0) { | |
setImageResource(mDefaultImageId); | |
} else { | |
setImageBitmap(null); | |
} | |
} | |
@Override | |
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { | |
super.onLayout(changed, left, top, right, bottom); | |
loadImageIfNecessary(true); | |
} | |
@Override | |
protected void onDetachedFromWindow() { | |
if (mMultiImageContainer != null) { | |
// If the view was bound to an image request, cancel it and clear | |
// out the image from the view. | |
mMultiImageContainer.cancelRequests(); | |
setImageBitmap(null); | |
// also clear out the container so we can reload the image if | |
// necessary. | |
mMultiImageContainer = null; | |
} | |
setListener(null); | |
super.onDetachedFromWindow(); | |
} | |
@Override | |
protected void drawableStateChanged() { | |
super.drawableStateChanged(); | |
invalidate(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment