From b8963a023ceb009cbf7f73a4506e7af7e693a219 Mon Sep 17 00:00:00 2001 From: Sam Judd Date: Sat, 26 Apr 2014 10:29:12 -0700 Subject: [PATCH] Pull out request builders into separate classes --- .../bumptech/glide/GenericRequestBuilder.java | 389 ++++++++++ library/src/com/bumptech/glide/Glide.java | 669 ++---------------- .../src/com/bumptech/glide/ListPreloader.java | 6 +- .../com/bumptech/glide/RequestBuilder.java | 139 ++++ .../com/bumptech/glide/RequestListener.java | 49 ++ .../glide/resize/request/BitmapRequest.java | 4 +- .../resize/request/BitmapRequestBuilder.java | 6 +- .../glide/samples/flickr/FlickrPhotoGrid.java | 3 +- .../glide/samples/flickr/FlickrPhotoList.java | 3 +- 9 files changed, 649 insertions(+), 619 deletions(-) create mode 100644 library/src/com/bumptech/glide/GenericRequestBuilder.java create mode 100644 library/src/com/bumptech/glide/RequestBuilder.java create mode 100644 library/src/com/bumptech/glide/RequestListener.java diff --git a/library/src/com/bumptech/glide/GenericRequestBuilder.java b/library/src/com/bumptech/glide/GenericRequestBuilder.java new file mode 100644 index 000000000..a00620dd3 --- /dev/null +++ b/library/src/com/bumptech/glide/GenericRequestBuilder.java @@ -0,0 +1,389 @@ +package com.bumptech.glide; + +import android.content.Context; +import android.graphics.Bitmap; +import android.view.View; +import android.widget.ImageView; +import com.bumptech.glide.loader.bitmap.ImageVideoBitmapLoadFactory; +import com.bumptech.glide.loader.bitmap.ResourceBitmapLoadFactory; +import com.bumptech.glide.loader.bitmap.model.ModelLoader; +import com.bumptech.glide.loader.bitmap.model.ModelLoaderFactory; +import com.bumptech.glide.resize.Priority; +import com.bumptech.glide.resize.load.BitmapDecoder; +import com.bumptech.glide.resize.load.Downsampler; +import com.bumptech.glide.resize.load.MultiTransformation; +import com.bumptech.glide.resize.load.Transformation; +import com.bumptech.glide.resize.load.VideoBitmapDecoder; +import com.bumptech.glide.resize.request.BitmapRequestBuilder; +import com.bumptech.glide.resize.request.Request; +import com.bumptech.glide.resize.request.ThumbnailRequestCoordinator; +import com.bumptech.glide.resize.target.ImageViewTarget; +import com.bumptech.glide.resize.target.Target; + +import java.util.ArrayList; +import java.util.List; + +/** + * A generic class that can handle loading a bitmap either from an image or as a thumbnail from a video given + * models loaders to translate a model into generic resources for either an image or a video and decoders that can + * decode those resources into bitmaps. + * + * @param The type of model representing the image or video. + * @param The resource type that the image {@link ModelLoader} will provide that can be decoded + * by the image {@link BitmapDecoder}. + * @param The resource type that the video {@link ModelLoader} will provide that can be decoded + * by the video {@link BitmapDecoder}. + */ +public class GenericRequestBuilder { + private Context context; + private ModelLoaderFactory imageModelLoaderFactory; + private final List transformations = new ArrayList(); + private final ModelLoaderFactory videoModelLoaderFactory; + private final ModelType model; + + private int animationId; + private int placeholderId; + private int errorId; + private RequestListener requestListener; + private BitmapDecoder imageDecoder; + private BitmapDecoder videoDecoder; + private Float thumbSizeMultiplier; + private GenericRequestBuilder thumbnailRequestBuilder; + private Float sizeMultiplier = 1f; + + GenericRequestBuilder(Context context, ModelType model, + ModelLoaderFactory imageFactory, + ModelLoaderFactory videoFactory) { + if (context == null) { + throw new NullPointerException("Context can't be null"); + } + this.context = context; + + if (model == null ) { + throw new NullPointerException("Model can't be null"); + } + this.model = model; + + if (imageFactory == null && videoFactory == null) { + throw new NullPointerException("No ModelLoaderFactorys registered for either image or video type," + + " class=" + model.getClass()); + } + this.imageModelLoaderFactory = imageFactory; + this.videoModelLoaderFactory = videoFactory; + } + + /** + * Loads and displays the image retrieved by the given thumbnail request if it finishes before this request. + * Best used for loading thumbnail images that are smaller and will be loaded more quickly than the fullsize + * image. There are no guarantees about the order in which the requests will actually finish. However, if the + * thumb request completes after the full request, the thumb image will never replace the full image. + * + * @see #thumbnail(float) + * + *

+ * Note - Any options on the main request will not be passed on to the thumbnail request. For example, if + * you want an animation to occur when either the full image loads or the thumbnail loads, you need to call + * {@link #animate(int)} on both the thumb and the full request. For a simpler thumbnail option, see + * {@link #thumbnail(float)}. + *

+ * + *

+ * Only the thumbnail call on the main request will be obeyed. + *

+ * + * @param thumbnailRequest The request to use to load the thumbnail. + * @return This builder object. + */ + public GenericRequestBuilder thumbnail( + GenericRequestBuilder thumbnailRequest) { + this.thumbnailRequestBuilder = thumbnailRequest; + + return this; + } + + /** + * Loads an image in an identical manner to this request except with the dimensions of the target multiplied + * by the given size multiplier. If the thumbnail load completes before the fullsize load, the thumbnail will + * be shown. If the thumbnail load completes afer the fullsize load, the thumbnail will not be shown. + * + *

+ * Note - The thumbnail image will be smaller than the size requested so the target (or {@link ImageView}) + * must be able to scale the thumbnail appropriately. See {@link ImageView.ScaleType}. + *

+ * + *

+ * Almost all options will be copied from the original load, including the {@link ModelLoader}, + * {@link BitmapDecoder}, and {@link Transformation}s. However, {@link #placeholder(int)} and + * {@link #error(int)}, and {@link #listener(RequestListener)} will only be used on the fullsize load and + * will not be copied for the thumbnail load. + *

+ * + *

+ * Only the thumbnail call on the main request will be obeyed. + *

+ * + * @param sizeMultiplier The multiplier to apply to the {@link Target}'s dimensions when loading the thumbnail. + * @return This builder object. + */ + public GenericRequestBuilder thumbnail(float sizeMultiplier) { + if (sizeMultiplier < 0f || sizeMultiplier > 1f) { + throw new IllegalArgumentException("sizeMultiplier must be between 0 and 1"); + } + this.thumbSizeMultiplier = sizeMultiplier; + + return this; + } + + /** + * Applies a multiplier to the {@link Target}'s size before loading the image. Useful for loading thumbnails + * or trying to avoid loading huge bitmaps on devices with overly dense screens. + * + * @param sizeMultiplier The multiplier to apply to the {@link Target}'s dimensions when loading the image. + * @return This builder object. + */ + public GenericRequestBuilder sizeMultiplier( + float sizeMultiplier) { + if (sizeMultiplier < 0f || sizeMultiplier > 1f) { + throw new IllegalArgumentException("sizeMultiplier must be between 0 and 1"); + } + this.sizeMultiplier = sizeMultiplier; + + return this; + } + + /** + * Loads the image from the given resource type into an {@link Bitmap} using the given {@link BitmapDecoder}. + * + *

+ * Will be ignored if the data represented by the given model is not an image. + *

+ * + * @see Downsampler + * + * @param decoder The {@link BitmapDecoder} to use to decode the image resource. + * @return This RequestBuilder. + */ + public GenericRequestBuilder imageDecoder( + BitmapDecoder decoder) { + this.imageDecoder = decoder; + + return this; + } + + /** + * Loads the video from the given resource type into an {@link Bitmap} using the given {@link BitmapDecoder}. + * + *

+ * Will be ignored if the data represented by the given model is not a video. + *

+ * + * @see VideoBitmapDecoder + * + * @param decoder The {@link BitmapDecoder} to use to decode the video resource. + * @return This request. + */ + public GenericRequestBuilder videoDecoder( + BitmapDecoder decoder) { + this.videoDecoder = decoder; + + return this; + } + + /** + * Transform images using {@link Transformation#CENTER_CROP}. + * + * @return This RequestBuilder + */ + public GenericRequestBuilder centerCrop() { + return transform(Transformation.CENTER_CROP); + } + + /** + * Transform images using {@link Transformation#FIT_CENTER}. + * + * @return This RequestBuilder + */ + public GenericRequestBuilder fitCenter() { + return transform(Transformation.FIT_CENTER); + } + + + /** + * Transform images with the given {@link Transformation}. Appends this transformation onto any existing + * transformations + * + * @param transformation the transformation to apply. + * @return This RequestBuilder + */ + public GenericRequestBuilder transform( + Transformation transformation) { + transformations.add(transformation); + + return this; + } + + /** + * Sets an animation to run on the wrapped target when an image load finishes. Will only be run if the image + * was loaded asynchronously (ie was not in the memory cache) + * + * @param animationId The resource id of the animation to run + * @return This RequestBuilder + */ + public GenericRequestBuilder animate(int animationId) { + this.animationId = animationId; + + return this; + } + + /** + * Sets a resource to display while an image is loading + * + * @param resourceId The id of the resource to use as a placeholder + * @return This RequestBuilder + */ + public GenericRequestBuilder placeholder(int resourceId) { + this.placeholderId = resourceId; + + return this; + } + + /** + * Sets a resource to display if a load fails + * + * @param resourceId The id of the resource to use as a placeholder + * @return This request + */ + public GenericRequestBuilder error(int resourceId) { + this.errorId = resourceId; + + return this; + } + + /** + * Sets a RequestBuilder listener to monitor the image load. It's best to create a single instance of an + * exception handler per type of request (usually activity/fragment) rather than pass one in per request to + * avoid some redundant object allocation. + * + * @param requestListener The request listener to use + * @return This request + */ + public GenericRequestBuilder listener( + RequestListener requestListener) { + this.requestListener = requestListener; + + return this; + } + + /** + * Set the target the image will be loaded into. + * + * @param target The target to load te image for + * @return The given target. + */ + public Y into(Y target) { + Request previous = target.getRequest(); + if (previous != null) { + previous.clear(); + } + + Request request = buildRequest(target); + target.setRequest(request); + request.run(); + return target; + } + + /** + * Sets the {@link ImageView} the image will be loaded into, cancels any existing loads into the view, and frees + * any resources Glide has loaded into the view so they may be reused. + * + * @see Glide#clear(View) + * + * @param view The view to cancel previous loads for and load the new image into. + * @return The {@link ImageViewTarget} used to wrap the given {@link ImageView}. + */ + public ImageViewTarget into(ImageView view) { + return into(new ImageViewTarget(view)); + } + + private Request buildRequest(Y target) { + final Request result; + if (thumbnailRequestBuilder != null) { + ThumbnailRequestCoordinator requestCoordinator = new ThumbnailRequestCoordinator(); + Request fullRequest = buildBitmapRequest(target) + .setRequestCoordinator(requestCoordinator) + .build(); + + if (thumbnailRequestBuilder.animationId == 0 && animationId != 0) { + thumbnailRequestBuilder.animationId = animationId; + } + + if (thumbnailRequestBuilder.requestListener == null && requestListener != null) { + thumbnailRequestBuilder.requestListener = requestListener; + } + Request thumbnailRequest = thumbnailRequestBuilder.buildBitmapRequest(target) + .setRequestCoordinator(requestCoordinator) + .build(); + + requestCoordinator.setRequests(fullRequest, thumbnailRequest); + result = requestCoordinator; + } else if (thumbSizeMultiplier != null) { + ThumbnailRequestCoordinator requestCoordinator = new ThumbnailRequestCoordinator(); + Request fullRequest = buildBitmapRequest(target) + .setRequestCoordinator(requestCoordinator) + .build(); + Request thumbnailRequest = buildBitmapRequest(target) + .setRequestCoordinator(requestCoordinator) + .setSizeMultiplier(thumbSizeMultiplier) + .build(); + requestCoordinator.setRequests(fullRequest, thumbnailRequest); + result = requestCoordinator; + } else { + result = buildBitmapRequest(target).build(); + } + return result; + } + + private BitmapRequestBuilder buildBitmapRequest(Y target) { + ModelLoader imageModelLoader = null; + if (imageModelLoaderFactory != null) { + imageModelLoader = imageModelLoaderFactory.build(context, Glide.get(context).getLoaderFactory()); + } + ModelLoader videoModelLoader = null; + if (videoModelLoaderFactory != null) { + videoModelLoader = videoModelLoaderFactory.build(context, Glide.get(context).getLoaderFactory()); + } + final Transformation transformation = getFinalTransformation(); + + return new BitmapRequestBuilder() + .setContext(context) + .setPriority(Priority.NORMAL) + .setImageManager(Glide.get(context).getImageManager()) + .setModel(model) + .setTarget(target) + .setBitmapLoadFactory( + new ImageVideoBitmapLoadFactory( + imageModelLoader != null && imageDecoder != null ? + new ResourceBitmapLoadFactory( + imageModelLoader, imageDecoder) : null, + videoModelLoader != null && videoDecoder != null ? + new ResourceBitmapLoadFactory( + videoModelLoader, videoDecoder) : null, + transformation)) + .setAnimation(animationId) + .setRequestListener(requestListener) + .setPlaceholderResource(placeholderId) + .setErrorResource(errorId) + .setSizeMultiplier(sizeMultiplier); + } + + private Transformation getFinalTransformation() { + switch (transformations.size()) { + case 0: + return Transformation.NONE; + case 1: + return transformations.get(0); + default: + return new MultiTransformation(transformations); + } + } +} diff --git a/library/src/com/bumptech/glide/Glide.java b/library/src/com/bumptech/glide/Glide.java index 222ba9717..517b637d7 100644 --- a/library/src/com/bumptech/glide/Glide.java +++ b/library/src/com/bumptech/glide/Glide.java @@ -6,10 +6,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.view.View; -import android.widget.ImageView; import com.android.volley.RequestQueue; -import com.bumptech.glide.loader.bitmap.ImageVideoBitmapLoadFactory; -import com.bumptech.glide.loader.bitmap.ResourceBitmapLoadFactory; import com.bumptech.glide.loader.bitmap.model.GenericLoaderFactory; import com.bumptech.glide.loader.bitmap.model.ModelLoader; import com.bumptech.glide.loader.bitmap.model.ModelLoaderFactory; @@ -25,19 +22,10 @@ import com.bumptech.glide.loader.bitmap.model.stream.StreamResourceLoader; import com.bumptech.glide.loader.bitmap.model.stream.StreamStringLoader; import com.bumptech.glide.loader.bitmap.model.stream.StreamUriLoader; import com.bumptech.glide.resize.ImageManager; -import com.bumptech.glide.resize.Priority; import com.bumptech.glide.resize.bitmap_recycle.BitmapPool; import com.bumptech.glide.resize.cache.DiskCache; import com.bumptech.glide.resize.cache.MemoryCache; -import com.bumptech.glide.resize.load.BitmapDecoder; -import com.bumptech.glide.resize.load.Downsampler; -import com.bumptech.glide.resize.load.MultiTransformation; -import com.bumptech.glide.resize.load.Transformation; -import com.bumptech.glide.resize.load.VideoBitmapDecoder; -import com.bumptech.glide.resize.request.BitmapRequestBuilder; import com.bumptech.glide.resize.request.Request; -import com.bumptech.glide.resize.request.ThumbnailRequestCoordinator; -import com.bumptech.glide.resize.target.ImageViewTarget; import com.bumptech.glide.resize.target.Target; import com.bumptech.glide.resize.target.ViewTarget; import com.bumptech.glide.volley.VolleyUrlLoader; @@ -45,8 +33,6 @@ import com.bumptech.glide.volley.VolleyUrlLoader; import java.io.File; import java.io.InputStream; import java.net.URL; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; /** @@ -63,52 +49,6 @@ public class Glide { private final RequestQueue requestQueue; private final ImageManager imageManager; - /** - * A class for monitoring the status of a request while images load. - * - * @param The type of the model being loaded - */ - public interface RequestListener { - - /** - * Called when an exception occurs during a load. Will only be called if we currently want to display an image - * for the given model in the given target. It is recommended to create a single instance per activity/fragment - * rather than instantiate a new object for each call to {@code Glide.load()} to avoid object churn. - * - *

- * It is safe to reload this or a different model or change what is displayed in the target at this point. - * For example: - *

-         * 
-         *     public void onException(Exception e, ModelType model, Target target) {
-         *         target.setPlaceholder(R.drawable.a_specific_error_for_my_exception);
-         *         Glide.load(model).into(target);
-         *     }
-         * 
-         * 
- *

- * - *

- * Note - if you want to reload this or any other model after an exception, you will need to include all - * relevant builder calls (like centerCrop, placeholder etc). - *

- * - * @param e The exception, or null - * @param model The model we were trying to load when the exception occurred - * @param target The {@link Target} we were trying to load the image into - */ - public abstract void onException(Exception e, T model, Target target); - - /** - * Called when a load completes successfully, immediately after - * {@link Target#onImageReady(android.graphics.Bitmap)}. - * - * @param model The specific model that was used to load the image. - * @param target The target the model was loaded into. - */ - public abstract void onImageReady(T model, Target target, boolean isFromMemoryCache, boolean isAnyImageSet); - } - /** * Get the singleton. * @@ -177,6 +117,37 @@ public class Glide { return requestQueue; } + /** + * Cancel any pending loads Glide may have for the target and free any resources (such as {@link Bitmap}s) that may + * have been loaded for the target so they may be reused. + * + * @param target The Target to cancel loads for. + */ + public static void clear(Target target) { + Request request = target.getRequest(); + if (request!= null) { + request.clear(); + } + } + + /** + * Cancel any pending loads Glide may have for the view and free any resources that may have been loaded for the + * view. + * + *

+ * Note that this will only work if {@link View#setTag(Object)} is not called on this view outside of Glide. + *

+ * + * @see #clear(Target). + * + * @param view The view to cancel loads and free resources for. + * @throws IllegalArgumentException if an object other than Glide's metadata is set as the view's tag. + */ + public static void clear(View view) { + Target viewTarget = new ClearTarget(view); + clear(viewTarget); + } + /** * Use the given factory to build a {@link ModelLoader} for models of the given class. Generally the best use of * this method is to replace one of the default factories or add an implementation for other similar low level @@ -211,11 +182,6 @@ public class Glide { } } - @SuppressWarnings("unchecked") - private ModelLoaderFactory getFactory(T model, Class resourceClass) { - return loaderFactory.getFactory((Class) model.getClass(), resourceClass); - } - /** * Build a {@link ModelLoader} for the given model class using a registered factory. * @@ -251,37 +217,6 @@ public class Glide { return buildModelLoader(modelClass, ParcelFileDescriptor.class, context); } - /** - * Cancel any pending loads Glide may have for the target and free any resources (such as {@link Bitmap}s) that may - * have been loaded for the target so they may be reused. - * - * @param target The Target to cancel loads for. - */ - public static void clear(Target target) { - Request request = target.getRequest(); - if (request!= null) { - request.clear(); - } - } - - /** - * Cancel any pending loads Glide may have for the view and free any resources that may have been loaded for the - * view. - * - *

- * Note that this will only work if {@link View#setTag(Object)} is not called on this view outside of Glide. - *

- * - * @see #clear(Target). - * - * @param view The view to cancel loads and free resources for. - * @throws IllegalArgumentException if an object other than Glide's metadata is set as the view's tag. - */ - public static void clear(View view) { - Target viewTarget = new CancelTarget(view); - clear(viewTarget); - } - /** * Begin a load with Glide by passing in a context. * @@ -292,6 +227,33 @@ public class Glide { return new ModelRequest(context); } + @SuppressWarnings("unchecked") + ModelLoaderFactory getFactory(T model, Class resourceClass) { + return loaderFactory.getFactory((Class) model.getClass(), resourceClass); + } + + GenericLoaderFactory getLoaderFactory() { + return loaderFactory; + } + + private static ModelLoaderFactory modelLoaderToFactory(final ModelLoader modelLoader) { + return new ModelLoaderFactory() { + @Override + public ModelLoader build(Context context, GenericLoaderFactory factories) { + return modelLoader; + } + + @SuppressWarnings("unchecked") + @Override + public Class> loaderClass() { + return (Class>) modelLoader.getClass(); + } + + @Override + public void teardown() { } + }; + } + /** * A {@link RequestBuilder} builder that returns a request for a model that represents an image. */ @@ -507,519 +469,8 @@ public class Glide { } } - /** - * A class for creating a request to load a bitmap for an image or from a video. Sets a variety of type independent - * options including resizing, animations, and placeholders. - * - * @param The type of model that will be loaded into the target. - */ - @SuppressWarnings("unused") //public api - public static class RequestBuilder extends - GenericRequestBuilder { - private RequestBuilder(Context context, ModelType model) { - this(context, model, Glide.get(context).getFactory(model, InputStream.class), - Glide.get(context).getFactory(model, ParcelFileDescriptor.class)); - } - - private RequestBuilder(Context context, ModelType model, - ModelLoaderFactory imageFactory, - ModelLoaderFactory videoFactory) { - super(context, model, imageFactory, videoFactory); - approximate().videoDecoder(new VideoBitmapDecoder()); - } - - /** - * Load images at a size near the size of the target using {@link Downsampler#AT_LEAST}. - * - * @see #downsample(com.bumptech.glide.resize.load.Downsampler) - * - * @return This RequestBuilder - */ - public RequestBuilder approximate() { - return downsample(Downsampler.AT_LEAST); - } - - /** - * Load images at their original size using {@link Downsampler#NONE}. - * - * @see #downsample(com.bumptech.glide.resize.load.Downsampler) - * - * @return This RequestBuilder - */ - public RequestBuilder asIs() { - return downsample(Downsampler.NONE); - } - - /** - * Load images using the given {@link Downsampler}. Replaces any existing image decoder. Defaults to - * {@link Downsampler#AT_LEAST}. Will be ignored if the data represented by the model is a video. - * - * @see #imageDecoder - * @see #videoDecoder(BitmapDecoder) - * - * @param downsampler The downsampler - * @return This RequestBuilder - */ - public RequestBuilder downsample(Downsampler downsampler) { - super.imageDecoder(downsampler); - return this; - } - - public RequestBuilder thumbnail(float sizeMultiplier) { - super.thumbnail(sizeMultiplier); - return this; - } - - public RequestBuilder thumbnail(RequestBuilder thumbnailRequest) { - super.thumbnail(thumbnailRequest); - return this; - } - - public RequestBuilder sizeMultiplier(float sizeMultiplier) { - super.sizeMultiplier(sizeMultiplier); - return this; - } - - @Override - public RequestBuilder imageDecoder(BitmapDecoder decoder) { - super.imageDecoder(decoder); - return this; - } - - @Override - public RequestBuilder videoDecoder(BitmapDecoder decoder) { - super.videoDecoder(decoder); - return this; - } - - @Override - public RequestBuilder centerCrop() { - super.centerCrop(); - return this; - } - - @Override - public RequestBuilder fitCenter() { - super.fitCenter(); - return this; - } - - @Override - public RequestBuilder transform(Transformation transformation) { - super.transform(transformation); - return this; - } - - @Override - public RequestBuilder animate(int animationId) { - super.animate(animationId); - return this; - } - - @Override - public RequestBuilder placeholder(int resourceId) { - super.placeholder(resourceId); - return this; - } - - @Override - public RequestBuilder error(int resourceId) { - super.error(resourceId); - return this; - } - - @Override - public RequestBuilder listener(RequestListener requestListener) { - super.listener(requestListener); - return this; - } - } - - /** - * A generic class that can handle loading a bitmap either from an image or as a thumbnail from a video given - * models loaders to translate a model into generic resources for either an image or a video and decoders that can - * decode those resources into bitmaps. - * - * @param The type of model representing the image or video. - * @param The resource type that the image {@link ModelLoader} will provide that can be decoded - * by the image {@link BitmapDecoder}. - * @param The resource type that the video {@link ModelLoader} will provide that can be decoded - * by the video {@link BitmapDecoder}. - */ - private static class GenericRequestBuilder { - private Context context; - private ModelLoaderFactory imageModelLoaderFactory; - private final List transformations = new ArrayList(); - private final ModelLoaderFactory videoModelLoaderFactory; - private final ModelType model; - - private int animationId; - private int placeholderId; - private int errorId; - private RequestListener requestListener; - private BitmapDecoder imageDecoder; - private BitmapDecoder videoDecoder; - private Float thumbSizeMultiplier; - private GenericRequestBuilder thumbnailRequestBuilder; - private Float sizeMultiplier = 1f; - - private GenericRequestBuilder(Context context, ModelType model, - ModelLoaderFactory imageFactory, - ModelLoaderFactory videoFactory) { - if (context == null) { - throw new NullPointerException("Context can't be null"); - } - this.context = context; - - if (model == null ) { - throw new NullPointerException("Model can't be null"); - } - this.model = model; - - if (imageFactory == null && videoFactory == null) { - throw new NullPointerException("No ModelLoaderFactorys registered for either image or video type," - + " class=" + model.getClass()); - } - this.imageModelLoaderFactory = imageFactory; - this.videoModelLoaderFactory = videoFactory; - } - - /** - * Loads and displays the image retrieved by the given thumbnail request if it finishes before this request. - * Best used for loading thumbnail images that are smaller and will be loaded more quickly than the fullsize - * image. There are no guarantees about the order in which the requests will actually finish. However, if the - * thumb request completes after the full request, the thumb image will never replace the full image. - * - * @see #thumbnail(float) - * - *

- * Note - Any options on the main request will not be passed on to the thumbnail request. For example, if - * you want an animation to occur when either the full image loads or the thumbnail loads, you need to call - * {@link #animate(int)} on both the thumb and the full request. For a simpler thumbnail option, see - * {@link #thumbnail(float)}. - *

- * - *

- * Only the thumbnail call on the main request will be obeyed. - *

- * - * @param thumbnailRequest The request to use to load the thumbnail. - * @return This builder object. - */ - public GenericRequestBuilder thumbnail( - GenericRequestBuilder thumbnailRequest) { - this.thumbnailRequestBuilder = thumbnailRequest; - - return this; - } - - /** - * Loads an image in an identical manner to this request except with the dimensions of the target multiplied - * by the given size multiplier. If the thumbnail load completes before the fullsize load, the thumbnail will - * be shown. If the thumbnail load completes afer the fullsize load, the thumbnail will not be shown. - * - *

- * Note - The thumbnail image will be smaller than the size requested so the target (or {@link ImageView}) - * must be able to scale the thumbnail appropriately. See {@link ImageView.ScaleType}. - *

- * - *

- * Almost all options will be copied from the original load, including the {@link ModelLoader}, - * {@link BitmapDecoder}, and {@link Transformation}s. However, {@link #placeholder(int)} and - * {@link #error(int)}, and {@link #listener(RequestListener)} will only be used on the fullsize load and - * will not be copied for the thumbnail load. - *

- * - *

- * Only the thumbnail call on the main request will be obeyed. - *

- * - * @param sizeMultiplier The multiplier to apply to the {@link Target}'s dimensions when loading the thumbnail. - * @return This builder object. - */ - public GenericRequestBuilder thumbnail(float sizeMultiplier) { - if (sizeMultiplier < 0f || sizeMultiplier > 1f) { - throw new IllegalArgumentException("sizeMultiplier must be between 0 and 1"); - } - this.thumbSizeMultiplier = sizeMultiplier; - - return this; - } - - /** - * Applies a multiplier to the {@link Target}'s size before loading the image. Useful for loading thumbnails - * or trying to avoid loading huge bitmaps on devices with overly dense screens. - * - * @param sizeMultiplier The multiplier to apply to the {@link Target}'s dimensions when loading the image. - * @return This builder object. - */ - public GenericRequestBuilder sizeMultiplier( - float sizeMultiplier) { - if (sizeMultiplier < 0f || sizeMultiplier > 1f) { - throw new IllegalArgumentException("sizeMultiplier must be between 0 and 1"); - } - this.sizeMultiplier = sizeMultiplier; - - return this; - } - - /** - * Loads the image from the given resource type into an {@link Bitmap} using the given {@link BitmapDecoder}. - * - *

- * Will be ignored if the data represented by the given model is not an image. - *

- * - * @see Downsampler - * - * @param decoder The {@link BitmapDecoder} to use to decode the image resource. - * @return This RequestBuilder. - */ - public GenericRequestBuilder imageDecoder( - BitmapDecoder decoder) { - this.imageDecoder = decoder; - - return this; - } - - /** - * Loads the video from the given resource type into an {@link Bitmap} using the given {@link BitmapDecoder}. - * - *

- * Will be ignored if the data represented by the given model is not a video. - *

- * - * @see VideoBitmapDecoder - * - * @param decoder The {@link BitmapDecoder} to use to decode the video resource. - * @return This request. - */ - public GenericRequestBuilder videoDecoder( - BitmapDecoder decoder) { - this.videoDecoder = decoder; - - return this; - } - - /** - * Transform images using {@link Transformation#CENTER_CROP}. - * - * @return This RequestBuilder - */ - public GenericRequestBuilder centerCrop() { - return transform(Transformation.CENTER_CROP); - } - - /** - * Transform images using {@link Transformation#FIT_CENTER}. - * - * @return This RequestBuilder - */ - public GenericRequestBuilder fitCenter() { - return transform(Transformation.FIT_CENTER); - } - - - /** - * Transform images with the given {@link Transformation}. Appends this transformation onto any existing - * transformations - * - * @param transformation the transformation to apply. - * @return This RequestBuilder - */ - public GenericRequestBuilder transform( - Transformation transformation) { - transformations.add(transformation); - - return this; - } - - /** - * Sets an animation to run on the wrapped target when an image load finishes. Will only be run if the image - * was loaded asynchronously (ie was not in the memory cache) - * - * @param animationId The resource id of the animation to run - * @return This RequestBuilder - */ - public GenericRequestBuilder animate(int animationId) { - this.animationId = animationId; - - return this; - } - - /** - * Sets a resource to display while an image is loading - * - * @param resourceId The id of the resource to use as a placeholder - * @return This RequestBuilder - */ - public GenericRequestBuilder placeholder(int resourceId) { - this.placeholderId = resourceId; - - return this; - } - - /** - * Sets a resource to display if a load fails - * - * @param resourceId The id of the resource to use as a placeholder - * @return This request - */ - public GenericRequestBuilder error(int resourceId) { - this.errorId = resourceId; - - return this; - } - - /** - * Sets a RequestBuilder listener to monitor the image load. It's best to create a single instance of an - * exception handler per type of request (usually activity/fragment) rather than pass one in per request to - * avoid some redundant object allocation. - * - * @param requestListener The request listener to use - * @return This request - */ - public GenericRequestBuilder listener( - RequestListener requestListener) { - this.requestListener = requestListener; - - return this; - } - - /** - * Set the target the image will be loaded into. - * - * @param target The target to load te image for - * @return The given target. - */ - public Y into(Y target) { - Request previous = target.getRequest(); - if (previous != null) { - previous.clear(); - } - - Request request = buildRequest(target); - target.setRequest(request); - request.run(); - return target; - } - - /** - * Sets the {@link ImageView} the image will be loaded into, cancels any existing loads into the view, and frees - * any resources Glide has loaded into the view so they may be reused. - * - * @see #clear(View) - * - * @param view The view to cancel previous loads for and load the new image into. - * @return The {@link ImageViewTarget} used to wrap the given {@link ImageView}. - */ - public ImageViewTarget into(ImageView view) { - return into(new ImageViewTarget(view)); - } - - private Request buildRequest(Y target) { - final Request result; - if (thumbnailRequestBuilder != null) { - ThumbnailRequestCoordinator requestCoordinator = new ThumbnailRequestCoordinator(); - Request fullRequest = buildBitmapRequest(target) - .setRequestCoordinator(requestCoordinator) - .build(); - - if (thumbnailRequestBuilder.animationId == 0 && animationId != 0) { - thumbnailRequestBuilder.animationId = animationId; - } - - if (thumbnailRequestBuilder.requestListener == null && requestListener != null) { - thumbnailRequestBuilder.requestListener = requestListener; - } - Request thumbnailRequest = thumbnailRequestBuilder.buildBitmapRequest(target) - .setRequestCoordinator(requestCoordinator) - .build(); - - requestCoordinator.setRequests(fullRequest, thumbnailRequest); - result = requestCoordinator; - } else if (thumbSizeMultiplier != null) { - ThumbnailRequestCoordinator requestCoordinator = new ThumbnailRequestCoordinator(); - Request fullRequest = buildBitmapRequest(target) - .setRequestCoordinator(requestCoordinator) - .build(); - Request thumbnailRequest = buildBitmapRequest(target) - .setRequestCoordinator(requestCoordinator) - .setSizeMultiplier(thumbSizeMultiplier) - .build(); - requestCoordinator.setRequests(fullRequest, thumbnailRequest); - result = requestCoordinator; - } else { - result = buildBitmapRequest(target).build(); - } - return result; - } - - private BitmapRequestBuilder buildBitmapRequest(Y target) { - ModelLoader imageModelLoader = null; - if (imageModelLoaderFactory != null) { - imageModelLoader = imageModelLoaderFactory.build(context, Glide.get(context).loaderFactory); - } - ModelLoader videoModelLoader = null; - if (videoModelLoaderFactory != null) { - videoModelLoader = videoModelLoaderFactory.build(context, Glide.get(context).loaderFactory); - } - final Transformation transformation = getFinalTransformation(); - - return new BitmapRequestBuilder() - .setContext(context) - .setPriority(Priority.NORMAL) - .setImageManager(Glide.get(context).imageManager) - .setModel(model) - .setTarget(target) - .setBitmapLoadFactory( - new ImageVideoBitmapLoadFactory( - imageModelLoader != null && imageDecoder != null ? - new ResourceBitmapLoadFactory( - imageModelLoader, imageDecoder) : null, - videoModelLoader != null && videoDecoder != null ? - new ResourceBitmapLoadFactory( - videoModelLoader, videoDecoder) : null, - transformation)) - .setAnimation(animationId) - .setRequestListener(requestListener) - .setPlaceholderResource(placeholderId) - .setErrorResource(errorId) - .setSizeMultiplier(sizeMultiplier); - } - - private Transformation getFinalTransformation() { - switch (transformations.size()) { - case 0: - return Transformation.NONE; - case 1: - return transformations.get(0); - default: - return new MultiTransformation(transformations); - } - } - } - - private static ModelLoaderFactory modelLoaderToFactory(final ModelLoader modelLoader) { - return new ModelLoaderFactory() { - @Override - public ModelLoader build(Context context, GenericLoaderFactory factories) { - return modelLoader; - } - - @SuppressWarnings("unchecked") - @Override - public Class> loaderClass() { - return (Class>) modelLoader.getClass(); - } - - @Override - public void teardown() { } - }; - } - - private static class CancelTarget extends ViewTarget { - public CancelTarget(View view) { + private static class ClearTarget extends ViewTarget { + public ClearTarget(View view) { super(view); } diff --git a/library/src/com/bumptech/glide/ListPreloader.java b/library/src/com/bumptech/glide/ListPreloader.java index e4af5a999..6c244a70e 100644 --- a/library/src/com/bumptech/glide/ListPreloader.java +++ b/library/src/com/bumptech/glide/ListPreloader.java @@ -83,9 +83,9 @@ public abstract class ListPreloader implements AbsListView.OnScrollListener { * target and context will be provided by the preloader. * * @param item The model to load. - * @return A non null {@link Glide.RequestBuilder}. + * @return A non null {@link RequestBuilder}. */ - protected abstract Glide.RequestBuilder getRequest(T item); + protected abstract RequestBuilder getRequestBuilder(T item); private void preload(int start, boolean increasing) { if (isIncreasing != increasing) { @@ -130,7 +130,7 @@ public abstract class ListPreloader implements AbsListView.OnScrollListener { final T item = items.get(position); final int[] dimensions = getDimensions(item); if (dimensions != null) { - getRequest(item).into(preloadTargetQueue.next(dimensions[0], dimensions[1])); + getRequestBuilder(item).into(preloadTargetQueue.next(dimensions[0], dimensions[1])); } } diff --git a/library/src/com/bumptech/glide/RequestBuilder.java b/library/src/com/bumptech/glide/RequestBuilder.java new file mode 100644 index 000000000..18ebb68c8 --- /dev/null +++ b/library/src/com/bumptech/glide/RequestBuilder.java @@ -0,0 +1,139 @@ +package com.bumptech.glide; + +import android.content.Context; +import android.os.ParcelFileDescriptor; +import com.bumptech.glide.loader.bitmap.model.ModelLoaderFactory; +import com.bumptech.glide.resize.load.BitmapDecoder; +import com.bumptech.glide.resize.load.Downsampler; +import com.bumptech.glide.resize.load.Transformation; +import com.bumptech.glide.resize.load.VideoBitmapDecoder; + +import java.io.InputStream; + +/** + * A class for creating a request to load a bitmap for an image or from a video. Sets a variety of type independent + * options including resizing, animations, and placeholders. + * + * @param The type of model that will be loaded into the target. + */ +@SuppressWarnings("unused") //public api +public class RequestBuilder extends GenericRequestBuilder { + + RequestBuilder(Context context, ModelType model) { + this(context, model, Glide.get(context).getFactory(model, InputStream.class), + Glide.get(context).getFactory(model, ParcelFileDescriptor.class)); + } + + RequestBuilder(Context context, ModelType model, ModelLoaderFactory imageFactory, + ModelLoaderFactory videoFactory) { + super(context, model, imageFactory, videoFactory); + + approximate().videoDecoder(new VideoBitmapDecoder()); + } + + /** + * Load images at a size near the size of the target using {@link Downsampler#AT_LEAST}. + * + * @see #downsample(com.bumptech.glide.resize.load.Downsampler) + * + * @return This RequestBuilder + */ + public RequestBuilder approximate() { + return downsample(Downsampler.AT_LEAST); + } + + /** + * Load images at their original size using {@link Downsampler#NONE}. + * + * @see #downsample(com.bumptech.glide.resize.load.Downsampler) + * + * @return This RequestBuilder + */ + public RequestBuilder asIs() { + return downsample(Downsampler.NONE); + } + + /** + * Load images using the given {@link Downsampler}. Replaces any existing image decoder. Defaults to + * {@link Downsampler#AT_LEAST}. Will be ignored if the data represented by the model is a video. + * + * @see #imageDecoder + * @see #videoDecoder(BitmapDecoder) + * + * @param downsampler The downsampler + * @return This RequestBuilder + */ + public RequestBuilder downsample(Downsampler downsampler) { + super.imageDecoder(downsampler); + return this; + } + + public RequestBuilder thumbnail(float sizeMultiplier) { + super.thumbnail(sizeMultiplier); + return this; + } + + public RequestBuilder thumbnail(RequestBuilder thumbnailRequest) { + super.thumbnail(thumbnailRequest); + return this; + } + + public RequestBuilder sizeMultiplier(float sizeMultiplier) { + super.sizeMultiplier(sizeMultiplier); + return this; + } + + @Override + public RequestBuilder imageDecoder(BitmapDecoder decoder) { + super.imageDecoder(decoder); + return this; + } + + @Override + public RequestBuilder videoDecoder(BitmapDecoder decoder) { + super.videoDecoder(decoder); + return this; + } + + @Override + public RequestBuilder centerCrop() { + super.centerCrop(); + return this; + } + + @Override + public RequestBuilder fitCenter() { + super.fitCenter(); + return this; + } + + @Override + public RequestBuilder transform(Transformation transformation) { + super.transform(transformation); + return this; + } + + @Override + public RequestBuilder animate(int animationId) { + super.animate(animationId); + return this; + } + + @Override + public RequestBuilder placeholder(int resourceId) { + super.placeholder(resourceId); + return this; + } + + @Override + public RequestBuilder error(int resourceId) { + super.error(resourceId); + return this; + } + + @Override + public RequestBuilder listener(RequestListener requestListener) { + super.listener(requestListener); + return this; + } +} diff --git a/library/src/com/bumptech/glide/RequestListener.java b/library/src/com/bumptech/glide/RequestListener.java new file mode 100644 index 000000000..c2df94558 --- /dev/null +++ b/library/src/com/bumptech/glide/RequestListener.java @@ -0,0 +1,49 @@ +package com.bumptech.glide; + +import com.bumptech.glide.resize.target.Target; + +/** + * A class for monitoring the status of a request while images load. + * + * @param The type of the model being loaded + */ +public interface RequestListener { + + /** + * Called when an exception occurs during a load. Will only be called if we currently want to display an image + * for the given model in the given target. It is recommended to create a single instance per activity/fragment + * rather than instantiate a new object for each call to {@code Glide.load()} to avoid object churn. + * + *

+ * It is safe to reload this or a different model or change what is displayed in the target at this point. + * For example: + *

+     * 
+     *     public void onException(Exception e, ModelType model, Target target) {
+     *         target.setPlaceholder(R.drawable.a_specific_error_for_my_exception);
+     *         Glide.load(model).into(target);
+     *     }
+     * 
+     * 
+ *

+ * + *

+ * Note - if you want to reload this or any other model after an exception, you will need to include all + * relevant builder calls (like centerCrop, placeholder etc). + *

+ * + * @param e The exception, or null + * @param model The model we were trying to load when the exception occurred + * @param target The {@link Target} we were trying to load the image into + */ + public abstract void onException(Exception e, T model, Target target); + + /** + * Called when a load completes successfully, immediately after + * {@link Target#onImageReady(android.graphics.Bitmap)}. + * + * @param model The specific model that was used to load the image. + * @param target The target the model was loaded into. + */ + public abstract void onImageReady(T model, Target target, boolean isFromMemoryCache, boolean isAnyImageSet); +} diff --git a/library/src/com/bumptech/glide/resize/request/BitmapRequest.java b/library/src/com/bumptech/glide/resize/request/BitmapRequest.java index 6f3564a0e..e60dd5eed 100644 --- a/library/src/com/bumptech/glide/resize/request/BitmapRequest.java +++ b/library/src/com/bumptech/glide/resize/request/BitmapRequest.java @@ -6,7 +6,7 @@ import android.graphics.drawable.Drawable; import android.util.Log; import android.view.animation.Animation; import android.view.animation.AnimationUtils; -import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestListener; import com.bumptech.glide.loader.bitmap.BitmapLoadFactory; import com.bumptech.glide.resize.ImageManager; import com.bumptech.glide.resize.Metadata; @@ -33,7 +33,7 @@ public class BitmapRequest implements Request, ImageManager.LoadedCallback, T private final ImageManager imageManager; private final Priority priority; private final Target target; - private final Glide.RequestListener requestListener; + private final RequestListener requestListener; private final float sizeMultiplier; private Drawable placeholderDrawable; diff --git a/library/src/com/bumptech/glide/resize/request/BitmapRequestBuilder.java b/library/src/com/bumptech/glide/resize/request/BitmapRequestBuilder.java index b39a31097..db19ee30e 100644 --- a/library/src/com/bumptech/glide/resize/request/BitmapRequestBuilder.java +++ b/library/src/com/bumptech/glide/resize/request/BitmapRequestBuilder.java @@ -4,7 +4,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.view.animation.Animation; -import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestListener; import com.bumptech.glide.loader.bitmap.BitmapLoadFactory; import com.bumptech.glide.resize.ImageManager; import com.bumptech.glide.resize.Priority; @@ -24,7 +24,7 @@ public class BitmapRequestBuilder { float sizeMultiplier; Drawable placeholderDrawable; Drawable errorDrawable; - Glide.RequestListener requestListener; + RequestListener requestListener; Animation animation; int placeholderResourceId; int errorResourceId; @@ -67,7 +67,7 @@ public class BitmapRequestBuilder { return this; } - public BitmapRequestBuilder setRequestListener(Glide.RequestListener requestListener) { + public BitmapRequestBuilder setRequestListener(RequestListener requestListener) { this.requestListener = requestListener; return this; } diff --git a/samples/flickr/src/com/bumptech/glide/samples/flickr/FlickrPhotoGrid.java b/samples/flickr/src/com/bumptech/glide/samples/flickr/FlickrPhotoGrid.java index 880c547ec..9f81d117a 100644 --- a/samples/flickr/src/com/bumptech/glide/samples/flickr/FlickrPhotoGrid.java +++ b/samples/flickr/src/com/bumptech/glide/samples/flickr/FlickrPhotoGrid.java @@ -11,6 +11,7 @@ import android.widget.ImageView; import com.actionbarsherlock.app.SherlockFragment; import com.bumptech.glide.Glide; import com.bumptech.glide.ListPreloader; +import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.loader.bitmap.model.Cache; import com.bumptech.glide.samples.flickr.api.Photo; @@ -81,7 +82,7 @@ public class FlickrPhotoGrid extends SherlockFragment implements PhotoViewer { } @Override - protected Glide.RequestBuilder getRequest(Photo item) { + protected RequestBuilder getRequestBuilder(Photo item) { return Glide.with(context) .using(new FlickrModelLoader(getActivity(), urlCache)) .load(item) diff --git a/samples/flickr/src/com/bumptech/glide/samples/flickr/FlickrPhotoList.java b/samples/flickr/src/com/bumptech/glide/samples/flickr/FlickrPhotoList.java index 2c0944e7a..5446208b3 100644 --- a/samples/flickr/src/com/bumptech/glide/samples/flickr/FlickrPhotoList.java +++ b/samples/flickr/src/com/bumptech/glide/samples/flickr/FlickrPhotoList.java @@ -12,6 +12,7 @@ import android.widget.TextView; import com.actionbarsherlock.app.SherlockFragment; import com.bumptech.glide.Glide; import com.bumptech.glide.ListPreloader; +import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.loader.bitmap.model.Cache; import com.bumptech.glide.samples.flickr.api.Photo; @@ -91,7 +92,7 @@ public class FlickrPhotoList extends SherlockFragment implements PhotoViewer { } @Override - protected Glide.RequestBuilder getRequest(Photo item) { + protected RequestBuilder getRequestBuilder(Photo item) { return Glide.with(context) .using(new FlickrModelLoader(getActivity(), urlCache)) .load(item) -- GitLab