/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.volley.cache;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.net.http.AndroidHttpClient;
import android.os.Build;
import android.support.v4.app.FragmentActivity;
import android.widget.ImageView;
import com.android.volley.Cache;
import com.android.volley.Network;
import com.android.volley.RequestQueue;
import com.android.volley.cache.DiskLruBasedCache.ImageCacheParams;
import com.android.volley.error.VolleyError;
import com.android.volley.misc.NetUtils;
import com.android.volley.misc.Utils;
import com.android.volley.toolbox.BasicNetwork;
import com.android.volley.toolbox.HttpClientStack;
import com.android.volley.toolbox.HurlStack;
import com.android.volley.toolbox.ImageCache;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.ui.PhotoView;
import java.util.ArrayList;
/**
* A class that wraps up remote image loading requests using the Volley library combined with a
* memory cache. An single instance of this class should be created once when your Activity or
* Fragment is created, then use {@link #get(String, android.widget.ImageView)} or one of
* the variations to queue the image to be fetched and loaded from the network. Loading images
* in a {@link android.widget.ListView} or {@link android.widget.GridView} is also supported but
* you must store the {@link com.android.volley.Request} in your ViewHolder type class and pass it
* into loadImage to ensure the request is canceled as views are recycled.
*/
public class SimpleImageLoader extends ImageLoader {
private static final ColorDrawable transparentDrawable = new ColorDrawable(
android.R.color.transparent);
private static final int HALF_FADE_IN_TIME = Utils.ANIMATION_FADE_IN_TIME / 2;
protected static final String CACHE_DIR = "images";
private ArrayList<Drawable> mPlaceHolderDrawables;
private boolean mFadeInImage = true;
private int mMaxImageHeight = 0;
private int mMaxImageWidth = 0;
/**
* {@inheritDoc}
*/
public SimpleImageLoader(RequestQueue queue) {
this(queue, BitmapCache.getInstance(null));
}
/**
* {@inheritDoc}
*/
public SimpleImageLoader(RequestQueue queue, ImageCache imageCache) {
this(queue, imageCache, null);
}
/**
* {@inheritDoc}
*/
public SimpleImageLoader(RequestQueue queue, ImageCache imageCache, Resources resources) {
super(queue, imageCache, resources);
}
/**
* Creates an ImageLoader with Bitmap memory cache.
* @param activity
*/
public SimpleImageLoader(FragmentActivity activity) {
super(newRequestQueue(activity, null),
BitmapImageCache.getInstance(activity.getSupportFragmentManager()),
activity.getResources());
}
/**
* Creates an ImageLoader with Bitmap memory cache.
* @param activity
* @param imageCacheParams
*/
public SimpleImageLoader(FragmentActivity activity, ImageCacheParams imageCacheParams) {
super(newRequestQueue(activity, imageCacheParams),
BitmapImageCache.getInstance(activity.getSupportFragmentManager(), imageCacheParams),
activity.getResources());
}
/**
* Creates an ImageLoader with Bitmap memory cache.
* @param context
*/
public SimpleImageLoader(Context context) {
super(newRequestQueue(context, null), BitmapImageCache.getInstance(null),
context.getResources());
}
/**
* Creates an ImageLoader with Bitmap memory cache.
* @param context
* @param imageCacheParams
*/
public SimpleImageLoader(Context context, ImageCacheParams imageCacheParams) {
super(newRequestQueue(context, imageCacheParams),
BitmapImageCache.getInstance(null, imageCacheParams),
context.getResources());
}
/**
* Starts processing requests on the {@link RequestQueue}.
*/
public void startProcessingQueue() {
getRequestQueue().start();
}
/**
* Stops processing requests on the {@link RequestQueue}.
*/
public void stopProcessingQueue() {
getRequestQueue().stop();
}
/**
* Clears {@link Cache}.
*/
public void clearCache() {
getCache().clear();
}
/**
* Flushed {@link Cache} and clears {@link com.android.volley.toolbox.ImageCache}.
*/
public void flushCache() {
getImageCache().clear();
getCache().flush();
}
/**
* Closes {@link Cache}.
*/
public void closeCache() {
getCache().close();
}
public boolean isCached(String key) {
return getCache().get(key) != null;
}
@Deprecated
public void invalidate(String key) {
final String cacheKey = getCacheKey(key, mMaxImageWidth, mMaxImageHeight);
getImageCache().invalidateBitmap(cacheKey);
getCache().invalidate(key, true);
}
public void invalidate(String key, ImageView view) {
final String cacheKey = getCacheKey(key, mMaxImageWidth, mMaxImageHeight, view.getScaleType());
getImageCache().invalidateBitmap(cacheKey);
getCache().invalidate(key, true);
//default cache
invalidate(key);
}
public SimpleImageLoader setFadeInImage(boolean fadeInImage) {
mFadeInImage = fadeInImage;
return this;
}
public SimpleImageLoader setMaxImageSize(int maxImageWidth, int maxImageHeight) {
mMaxImageWidth = maxImageWidth;
mMaxImageHeight = maxImageHeight;
return this;
}
/**
* A default placeholder image while the image is being fetched and loaded.
* @param defaultPlaceHolderResId
* @return
*/
public SimpleImageLoader setDefaultDrawable(int defaultPlaceHolderResId){
mPlaceHolderDrawables = new ArrayList<Drawable>(1);
mPlaceHolderDrawables.add(defaultPlaceHolderResId == -1 ?
null : getResources().getDrawable(defaultPlaceHolderResId));
return this;
}
/**
* A default placeholder image while the image is being fetched and loaded.
* @param placeHolderDrawables
* @return
*/
public SimpleImageLoader setDefaultDrawables(ArrayList<Drawable> placeHolderDrawables){
mPlaceHolderDrawables = placeHolderDrawables;
return this;
}
public SimpleImageLoader setMaxImageSize(int maxImageSize) {
return setMaxImageSize(maxImageSize, maxImageSize);
}
public int getMaxImageWidth() {
return mMaxImageWidth;
}
public int getMaxImageHeight() {
return mMaxImageHeight;
}
//Get
public ImageContainer get(String requestUrl, ImageView imageView) {
return get(requestUrl, imageView, 0);
}
public ImageContainer get(String requestUrl, ImageView imageView, int maxImageWidth, int maxImageHeight) {
return get(requestUrl, imageView, mPlaceHolderDrawables != null ? mPlaceHolderDrawables.get(0) : null, maxImageWidth, maxImageHeight);
}
public ImageContainer get(String requestUrl, ImageView imageView, int placeHolderIndex) {
return get(requestUrl, imageView,
mPlaceHolderDrawables != null ? mPlaceHolderDrawables.get(placeHolderIndex) : null,
mMaxImageWidth, mMaxImageHeight);
}
public ImageContainer get(String requestUrl, ImageView imageView, Drawable placeHolder) {
return get(requestUrl, imageView, placeHolder, mMaxImageWidth, mMaxImageHeight);
}
public ImageContainer get(String requestUrl, ImageView imageView, Drawable placeHolder,
int maxWidth, int maxHeight) {
// Find any old image load request pending on this ImageView (in case this view was
// recycled)
ImageContainer imageContainer = imageView.getTag() != null &&
imageView.getTag() instanceof ImageContainer ?
(ImageContainer) imageView.getTag() : null;
// Find image url from prior request
String recycledImageUrl = imageContainer != null ? imageContainer.getRequestUrl() : null;
// If the new requestUrl is null or the new requestUrl is different to the previous
// recycled requestUrl
if (requestUrl == null || !requestUrl.equals(recycledImageUrl)) {
if (imageContainer != null) {
// Cancel previous image request
imageContainer.cancelRequest();
imageView.setTag(null);
}
if (requestUrl != null) {
// Queue new request to fetch image
imageContainer = get(requestUrl,
getImageListener(getResources(), imageView, placeHolder, mFadeInImage),
maxWidth, maxHeight, imageView.getScaleType());
// Store request in ImageView tag
imageView.setTag(imageContainer);
} else {
if(!(imageView instanceof PhotoView)){
imageView.setImageDrawable(placeHolder);
}
imageView.setTag(null);
}
}
return imageContainer;
}
//Set
public ImageContainer set(String requestUrl, ImageView imageView, Bitmap bitmap) {
return set(requestUrl, imageView, 0, bitmap);
}
public ImageContainer set(String requestUrl, ImageView imageView, int placeHolderIndex, Bitmap bitmap) {
return set(requestUrl, imageView,
mPlaceHolderDrawables != null ? mPlaceHolderDrawables.get(placeHolderIndex) : null,
mMaxImageWidth, mMaxImageHeight, bitmap);
}
public ImageContainer set(String requestUrl, ImageView imageView, Drawable placeHolder, Bitmap bitmap) {
return set(requestUrl, imageView, placeHolder, mMaxImageWidth, mMaxImageHeight, bitmap);
}
public ImageContainer set(String requestUrl, ImageView imageView, Drawable placeHolder,
int maxWidth, int maxHeight, Bitmap bitmap) {
// Find any old image load request pending on this ImageView (in case this view was
// recycled)
ImageContainer imageContainer = imageView.getTag() != null &&
imageView.getTag() instanceof ImageContainer ?
(ImageContainer) imageView.getTag() : null;
// Find image url from prior request
//String recycledImageUrl = imageContainer != null ? imageContainer.getRequestUrl() : null;
if (imageContainer != null) {
// Cancel previous image request
imageContainer.cancelRequest();
imageView.setTag(null);
}
if (requestUrl != null) {
// Queue new request to fetch image
imageContainer = set(requestUrl,
getImageListener(getResources(), imageView, placeHolder, mFadeInImage),
maxWidth, maxHeight, imageView.getScaleType(), bitmap);
// Store request in ImageView tag
imageView.setTag(imageContainer);
} else {
if(!(imageView instanceof PhotoView)){
imageView.setImageDrawable(placeHolder);
}
imageView.setTag(null);
}
return imageContainer;
}
public static ImageListener getImageListener(final Resources resources,
final ImageView imageView, final Drawable placeHolder, final boolean fadeInImage) {
return new ImageListener() {
@Override
public void onResponse(ImageContainer response, boolean isImmediate) {
imageView.setTag(null);
if (response.getBitmap() != null) {
if(imageView instanceof PhotoView){
setPhotoImageBitmap((PhotoView) imageView, response.getBitmap(), resources,
fadeInImage && !isImmediate);
}
else{
setImageBitmap(imageView, response.getBitmap(), resources,
fadeInImage && !isImmediate);
}
} else {
if(!(imageView instanceof PhotoView)){
imageView.setImageDrawable(placeHolder);
}
}
}
@Override
public void onErrorResponse(VolleyError volleyError) {
}
};
}
private static RequestQueue newRequestQueue(Context context, ImageCacheParams imageCacheParams) {
Network network = new BasicNetwork(
Utils.hasHoneycomb() ?
new HurlStack() :
new HttpClientStack(AndroidHttpClient.newInstance(
NetUtils.getUserAgent(context))));
Cache cache;
if(null != imageCacheParams){
cache = new DiskLruBasedCache(imageCacheParams);
}
else{
cache = new DiskLruBasedCache(Utils.getDiskCacheDir(context, CACHE_DIR));
}
RequestQueue queue = new RequestQueue(cache, network);
queue.start();
return queue;
}
/**
* Sets a {@link android.graphics.Bitmap} to an {@link android.widget.ImageView} using a
* fade-in animation. If there is a {@link android.graphics.drawable.Drawable} already set on
* the ImageView then use that as the image to fade from. Otherwise fade in from a transparent
* Drawable.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
private static void setImageBitmap(final ImageView imageView, final Bitmap bitmap,
Resources resources, boolean fadeIn) {
// If we're fading in and on HC MR1+
if (fadeIn && Utils.hasHoneycombMR1()) {
// Use ViewPropertyAnimator to run a simple fade in + fade out animation to update the
// ImageView
imageView.animate()
.scaleY(0.95f)
.scaleX(0.95f)
.alpha(0f)
.setDuration(imageView.getDrawable() == null ? 0 : HALF_FADE_IN_TIME)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
imageView.setImageBitmap(bitmap);
imageView.animate()
.alpha(1f)
.scaleY(1f)
.scaleX(1f)
.setDuration(HALF_FADE_IN_TIME)
.setListener(null);
}
});
} else if (fadeIn) {
// Otherwise use a TransitionDrawable to fade in
Drawable initialDrawable;
if (imageView.getDrawable() != null) {
initialDrawable = imageView.getDrawable();
} else {
initialDrawable = transparentDrawable;
}
BitmapDrawable bitmapDrawable = new BitmapDrawable(resources, bitmap);
// Use TransitionDrawable to fade in
final TransitionDrawable td =
new TransitionDrawable(new Drawable[] {
initialDrawable,
bitmapDrawable
});
imageView.setImageDrawable(td);
td.startTransition(Utils.ANIMATION_FADE_IN_TIME);
} else {
// No fade in, just set bitmap directly
imageView.setImageBitmap(bitmap);
}
}
/**
* Sets a {@link android.graphics.Bitmap} to an {@link android.widget.ImageView} using a
* fade-in animation. If there is a {@link android.graphics.drawable.Drawable} already set on
* the ImageView then use that as the image to fade from. Otherwise fade in from a transparent
* Drawable.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
private static void setPhotoImageBitmap(final PhotoView imageView, final Bitmap bitmap,
Resources resources, boolean fadeIn) {
// If we're fading in and on HC MR1+
if (fadeIn && Utils.hasHoneycombMR1()) {
// Use ViewPropertyAnimator to run a simple fade in + fade out animation to update the
// ImageView
imageView.animate()
.scaleY(0.95f)
.scaleX(0.95f)
.alpha(0f)
.setDuration(imageView.getDrawable() == null ? 0 : HALF_FADE_IN_TIME)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
imageView.bindPhoto(bitmap);
imageView.animate()
.alpha(1f)
.scaleY(1f)
.scaleX(1f)
.setDuration(HALF_FADE_IN_TIME)
.setListener(null);
}
});
} else if (fadeIn) {
// Otherwise use a TransitionDrawable to fade in
Drawable initialDrawable;
if (imageView.getDrawable() != null) {
initialDrawable = imageView.getDrawable();
} else {
initialDrawable = transparentDrawable;
}
BitmapDrawable bitmapDrawable = new BitmapDrawable(resources, bitmap);
// Use TransitionDrawable to fade in
final TransitionDrawable td =
new TransitionDrawable(new Drawable[] {
initialDrawable,
bitmapDrawable
});
imageView.bindDrawable(td);
td.startTransition(Utils.ANIMATION_FADE_IN_TIME);
} else {
// No fade in, just set bitmap directly
imageView.bindPhoto(bitmap);
}
}
/**
* Interface an activity can implement to provide an ImageLoader to its children fragments.
*/
public interface ImageLoaderProvider {
public SimpleImageLoader getImageLoaderInstance();
}
}