package com.bumptech.glide; import android.content.Context; import android.graphics.drawable.Drawable; import android.view.animation.Animation; import android.widget.ImageView; import com.bumptech.glide.load.Encoder; import com.bumptech.glide.load.Key; import com.bumptech.glide.load.MultiTransformation; import com.bumptech.glide.load.ResourceDecoder; import com.bumptech.glide.load.ResourceEncoder; import com.bumptech.glide.load.Transformation; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.resource.UnitTransformation; import com.bumptech.glide.load.resource.transcode.ResourceTranscoder; import com.bumptech.glide.manager.Lifecycle; import com.bumptech.glide.manager.RequestTracker; import com.bumptech.glide.provider.ChildLoadProvider; import com.bumptech.glide.provider.LoadProvider; import com.bumptech.glide.request.FutureTarget; import com.bumptech.glide.request.GenericRequest; import com.bumptech.glide.request.Request; import com.bumptech.glide.request.RequestCoordinator; import com.bumptech.glide.request.RequestFutureTarget; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.ThumbnailRequestCoordinator; import com.bumptech.glide.request.animation.GlideAnimationFactory; import com.bumptech.glide.request.animation.NoAnimation; import com.bumptech.glide.request.animation.ViewAnimationFactory; import com.bumptech.glide.request.animation.ViewPropertyAnimation; import com.bumptech.glide.request.animation.ViewPropertyAnimationFactory; import com.bumptech.glide.request.target.PreloadTarget; import com.bumptech.glide.request.target.Target; import com.bumptech.glide.signature.EmptySignature; import com.bumptech.glide.util.Util; import java.io.File; /** * A generic class that can handle setting options and staring loads for generic resource types. * * @param The type of model representing the resource. * @param The data type that the resource {@link com.bumptech.glide.load.model.ModelLoader} will provide that * can be decoded by the {@link com.bumptech.glide.load.ResourceDecoder}. * @param The type of the resource that will be loaded. * @param The type of resource the decoded resource will be transcoded to. */ public class GenericRequestBuilder implements Cloneable { protected final Class modelClass; protected final Context context; protected final Glide glide; protected final Class transcodeClass; protected final RequestTracker requestTracker; protected final Lifecycle lifecycle; private ChildLoadProvider loadProvider; private ModelType model; private Key signature = EmptySignature.obtain(); // model may occasionally be null, so to enforce that load() was called, set a boolean rather than relying on model // not to be null. private boolean isModelSet; private int placeholderId; private int errorId; private RequestListener requestListener; private Float thumbSizeMultiplier; private GenericRequestBuilder thumbnailRequestBuilder; private Float sizeMultiplier = 1f; private Drawable placeholderDrawable; private Drawable errorPlaceholder; private Priority priority = null; private boolean isCacheable = true; private GlideAnimationFactory animationFactory = NoAnimation.getFactory(); private int overrideHeight = -1; private int overrideWidth = -1; private DiskCacheStrategy diskCacheStrategy = DiskCacheStrategy.RESULT; private Transformation transformation = UnitTransformation.get(); private boolean isTransformationSet; GenericRequestBuilder(LoadProvider loadProvider, Class transcodeClass, GenericRequestBuilder other) { this(other.context, other.modelClass, loadProvider, transcodeClass, other.glide, other.requestTracker, other.lifecycle); this.model = other.model; this.isModelSet = other.isModelSet; this.signature = other.signature; this.diskCacheStrategy = other.diskCacheStrategy; this.isCacheable = other.isCacheable; } GenericRequestBuilder(Context context, Class modelClass, LoadProvider loadProvider, Class transcodeClass, Glide glide, RequestTracker requestTracker, Lifecycle lifecycle) { this.context = context; this.modelClass = modelClass; this.transcodeClass = transcodeClass; this.glide = glide; this.requestTracker = requestTracker; this.lifecycle = lifecycle; this.loadProvider = loadProvider != null ? new ChildLoadProvider(loadProvider) : null; if (context == null) { throw new NullPointerException("Context can't be null"); } if (modelClass != null && loadProvider == null) { throw new NullPointerException("LoadProvider must not be null"); } } /** * Loads and displays the resource retrieved by the given thumbnail request if it finishes before this request. * Best used for loading thumbnail resources that are smaller and will be loaded more quickly than the full size * resource. 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 resource will never replace the full resource. * * @see #thumbnail(float) * *

* Recursive calls to thumbnail are supported. *

* * @param thumbnailRequest The request to use to load the thumbnail. * @return This request builder. */ public GenericRequestBuilder thumbnail( GenericRequestBuilder thumbnailRequest) { this.thumbnailRequestBuilder = thumbnailRequest; return this; } /** * Loads a resource 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 resource will be smaller than the size requested so the target (or {@link ImageView}) * must be able to scale the thumbnail appropriately. See {@link android.widget.ImageView.ScaleType}. *

* *

* Almost all options will be copied from the original load, including the * {@link com.bumptech.glide.load.model.ModelLoader}, {@link com.bumptech.glide.load.ResourceDecoder}, 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. *

* *

* Recursive calls to thumbnail are supported. *

* * @param sizeMultiplier The multiplier to apply to the {@link Target}'s dimensions when loading the thumbnail. * @return This request builder. */ 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 resource. Useful for loading thumbnails * or trying to avoid loading huge resources (particularly {@link android.graphics.Bitmap}s on devices with overly * dense screens. * * @param sizeMultiplier The multiplier to apply to the {@link Target}'s dimensions when loading the resource. * @return This request builder. */ 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; } /** * Sets the {@link com.bumptech.glide.load.ResourceDecoder} to use to load the resource from the original data. * By default, this decoder will only be used if the final transformed resource is not in the disk cache. * * @see #cacheDecoder(com.bumptech.glide.load.ResourceDecoder) * @see com.bumptech.glide.load.engine.DiskCacheStrategy * * @param decoder The {@link com.bumptech.glide.load.ResourceDecoder} to use to decode the resource. * @return This request builder. */ public GenericRequestBuilder decoder( ResourceDecoder decoder) { // loadProvider will be null if model is null, in which case we're not going to load anything so it's ok to // ignore the decoder. if (loadProvider != null) { loadProvider.setSourceDecoder(decoder); } return this; } /** * Sets the {@link com.bumptech.glide.load.ResourceDecoder} to use to load the resource from the disk cache. By * default, this decoder will only be used if the final transformed resource is already in the disk cache. * * @see #decoder(com.bumptech.glide.load.ResourceDecoder) * @see com.bumptech.glide.load.engine.DiskCacheStrategy * * @param cacheDecoder The decoder to use. * @return This request builder. */ public GenericRequestBuilder cacheDecoder( ResourceDecoder cacheDecoder) { // loadProvider will be null if model is null, in which case we're not going to load anything so it's ok to // ignore the decoder. if (loadProvider != null) { loadProvider.setCacheDecoder(cacheDecoder); } return this; } /** * Sets the source encoder to use to encode the data retrieved by this request directly into cache. The returned * resource will then be decoded from the cached data. * * @see com.bumptech.glide.load.engine.DiskCacheStrategy * * @param sourceEncoder The encoder to use. * @return This request builder. */ public GenericRequestBuilder sourceEncoder( Encoder sourceEncoder) { if (loadProvider != null) { loadProvider.setSourceEncoder(sourceEncoder); } return this; } /** * Sets the {@link com.bumptech.glide.load.engine.DiskCacheStrategy} to use for this load. Defaults to * {@link com.bumptech.glide.load.engine.DiskCacheStrategy#RESULT}. * *

* For most applications {@link com.bumptech.glide.load.engine.DiskCacheStrategy#RESULT} is ideal. * Applications that use the same resource multiple times in multiple sizes and are willing to trade off some * speed and disk space in return for lower bandwidth usage may want to consider using * {@link com.bumptech.glide.load.engine.DiskCacheStrategy#SOURCE} or * {@link com.bumptech.glide.load.engine.DiskCacheStrategy#RESULT}. Any download only operations should * typically use {@link com.bumptech.glide.load.engine.DiskCacheStrategy#SOURCE}. *

* * @param strategy The strategy to use. * @return This request builder. */ public GenericRequestBuilder diskCacheStrategy( DiskCacheStrategy strategy) { this.diskCacheStrategy = strategy; return this; } /** * Sets the {@link com.bumptech.glide.load.Encoder} to use to encode the original data directly to cache. Will only * be used if the original data is not already in cache and if the * {@link com.bumptech.glide.load.engine.DiskCacheStrategy} is set to * {@link com.bumptech.glide.load.engine.DiskCacheStrategy#SOURCE} or * {@link com.bumptech.glide.load.engine.DiskCacheStrategy#ALL}. * * @see #sourceEncoder(com.bumptech.glide.load.Encoder) * @see com.bumptech.glide.load.engine.DiskCacheStrategy * * @param encoder The encoder to use. * @return This request builder. */ public GenericRequestBuilder encoder( ResourceEncoder encoder) { // loadProvider will be null if model is null, in which case we're not going to load anything so it's ok to // ignore the encoder. if (loadProvider != null) { loadProvider.setEncoder(encoder); } return this; } /** * Sets the priority for this load. * * @param priority A priority. * @return This request builder. */ public GenericRequestBuilder priority( Priority priority) { this.priority = priority; return this; } /** * Transform resources with the given {@link Transformation}s. Replaces any existing transformation or * transformations. * * @param transformations the transformations to apply in order. * @return This request builder. */ public GenericRequestBuilder transform( Transformation... transformations) { isTransformationSet = true; if (transformations.length == 1) { transformation = transformations[0]; } else { transformation = new MultiTransformation(transformations); } return this; } /** * Removes the current {@link com.bumptech.glide.load.Transformation}. * * @return This request builder. */ @SuppressWarnings("unchecked") public GenericRequestBuilder dontTransform() { Transformation transformation = UnitTransformation.get(); return transform(transformation); } /** * Sets the {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} to use for this load. * * @see com.bumptech.glide.load.resource.transcode.UnitTranscoder * @see com.bumptech.glide.load.resource.transcode.GlideBitmapDrawableTranscoder * @see com.bumptech.glide.load.resource.transcode.GifBitmapWrapperDrawableTranscoder * * @param transcoder The transcoder to use. * @return This request builder. */ public GenericRequestBuilder transcoder( ResourceTranscoder transcoder) { if (loadProvider != null) { loadProvider.setTranscoder(transcoder); } return this; } /** * Removes any existing animation set on the builder. Will be overridden by subsequent calls that set an animation. * @return This request builder. */ public GenericRequestBuilder dontAnimate() { GlideAnimationFactory animation = NoAnimation.getFactory(); return animate(animation); } /** * Sets an animation to run on the wrapped target when an resource load finishes. Will only be run if the resource * was loaded asynchronously (ie was not in the memory cache) * * @param animationId The resource id of the animation to run * @return This request builder. */ public GenericRequestBuilder animate(int animationId) { return animate(new ViewAnimationFactory(context, animationId)); } /** * Sets an animation to run on the wrapped target when a resource load finishes. Will only be run if the resource * was loaded asynchronously (ie was not in the memory cache) * * @see #animate(int) * @see #animate(com.bumptech.glide.request.animation.ViewPropertyAnimation.Animator) * * @deprecated If this builder is used for multiple loads, using this method will result in multiple view's being * asked to start an animation using a single {@link android.view.animation.Animation} object which results in * views animating repeatedly. Use {@link #animate(int)} or * {@link #animate(com.bumptech.glide.request.animation.ViewPropertyAnimation.Animator)}. Scheduled to be removed in * Glide 4.0. * @param animation The animation to run * @return This request builder. */ @Deprecated public GenericRequestBuilder animate(Animation animation) { return animate(new ViewAnimationFactory(animation)); } /** * Sets an animator to run a {@link android.view.ViewPropertyAnimator} on a view that the target may be wrapping * when a resource load finishes. Will only be run if the load was loaded asynchronously (ie was not in the * memory cache). * * @param animator The {@link com.bumptech.glide.request.animation.ViewPropertyAnimation.Animator} to run. * @return This request builder. */ public GenericRequestBuilder animate( ViewPropertyAnimation.Animator animator) { return animate(new ViewPropertyAnimationFactory(animator)); } GenericRequestBuilder animate( GlideAnimationFactory animationFactory) { if (animationFactory == null) { throw new NullPointerException("Animation factory must not be null!"); } this.animationFactory = animationFactory; return this; } /** * Sets an Android resource id for a {@link android.graphics.drawable.Drawable} resourceto display while a resource * is loading. * * @param resourceId The id of the resource to use as a placeholder * @return This request builder. */ public GenericRequestBuilder placeholder( int resourceId) { this.placeholderId = resourceId; return this; } /** * Sets an {@link android.graphics.drawable.Drawable} to display while a resource is loading. * * @param drawable The drawable to display as a placeholder. * @return This request builder. */ public GenericRequestBuilder placeholder( Drawable drawable) { this.placeholderDrawable = drawable; 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 builder. */ public GenericRequestBuilder error( int resourceId) { this.errorId = resourceId; return this; } /** * Sets a {@link Drawable} to display if a load fails. * * @param drawable The drawable to display. * @return This request builder. */ public GenericRequestBuilder error( Drawable drawable) { this.errorPlaceholder = drawable; return this; } /** * Sets a RequestBuilder listener to monitor the resource 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 builder. */ public GenericRequestBuilder listener( RequestListener requestListener) { this.requestListener = requestListener; return this; } /** * Allows the loaded resource to skip the memory cache. * *

* Note - this is not a guarantee. If a request is already pending for this resource and that request is not * also skipping the memory cache, the resource will be cached in memory. *

* * @param skip True to allow the resource to skip the memory cache. * @return This request builder. */ public GenericRequestBuilder skipMemoryCache(boolean skip) { this.isCacheable = !skip; return this; } /** * Overrides the {@link Target}'s width and height with the given values. This is useful almost exclusively for * thumbnails, and should only be used when you both need a very specific sized image and when it is impossible or * impractical to return that size from {@link Target#getSize(com.bumptech.glide.request.target.SizeReadyCallback)}. * * @param width The width in pixels to use to load the resource. * @param height The height in pixels to use to load the resource. * @return This request builder. */ public GenericRequestBuilder override(int width, int height) { if (width <= 0) { throw new IllegalArgumentException("Width must be > 0"); } if (height <= 0) { throw new IllegalArgumentException("Height must be > 0"); } this.overrideWidth = width; this.overrideHeight = height; return this; } /** * Sets some additional data to be mixed in to the memory and disk cache keys allowing the caller more control over * when cached data is invalidated. * *

* Note - The signature does not replace the cache key, it is purely additive. *

* * @see com.bumptech.glide.signature.StringSignature * * @param signature A unique non-null {@link com.bumptech.glide.load.Key} representing the current state of the * model that will be mixed in to the cache key. * @return This request builder. */ public GenericRequestBuilder signature(Key signature) { if (signature == null) { throw new NullPointerException("Signature must not be null"); } this.signature = signature; return this; } /** * Sets the specific model to load data for. * *

* This method must be called at least once before {@link #into(com.bumptech.glide.request.target.Target)} is * called. *

* * @param model The model to load data for, or null. * @return This request builder. */ public GenericRequestBuilder load(ModelType model) { this.model = model; isModelSet = true; return this; } /** * Returns a copy of this request builder with all of the options set so far on this builder. * *

* This method returns a "deep" copy in that all non-immutable arguments are copied such that changes to one * builder will not affect the other builder. However, in addition to immutable arguments, the current model * is not copied copied so changes to the model will affect both builders. *

*/ @SuppressWarnings("unchecked") @Override public GenericRequestBuilder clone() { try { GenericRequestBuilder clone = (GenericRequestBuilder) super.clone(); clone.loadProvider = loadProvider != null ? loadProvider.clone() : null; return clone; } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } /** * Set the target the resource will be loaded into. * * @see Glide#clear(com.bumptech.glide.request.target.Target) * * @param target The target to load the resource into. * @return The given target. */ public > Y into(Y target) { Util.assertMainThread(); if (target == null) { throw new IllegalArgumentException("You must pass in a non null Target"); } if (!isModelSet) { throw new IllegalArgumentException("You must first set a model (try #load())"); } Request previous = target.getRequest(); if (previous != null) { previous.clear(); requestTracker.removeRequest(previous); previous.recycle(); } Request request = buildRequest(target); target.setRequest(request); lifecycle.addListener(target); requestTracker.runRequest(request); return target; } /** * Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into the view, and frees * any resources Glide may have previously loaded into the view so they may be reused. * * @see Glide#clear(android.view.View) * * @param view The view to cancel previous loads for and load the new resource into. * @return The {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link ImageView}. */ public Target into(ImageView view) { Util.assertMainThread(); if (view == null) { throw new IllegalArgumentException("You must pass in a non null View"); } if (!isTransformationSet && view.getScaleType() != null) { switch (view.getScaleType()) { case CENTER_CROP: applyCenterCrop(); break; case FIT_CENTER: case FIT_START: case FIT_END: applyFitCenter(); break; //$CASES-OMITTED$ default: // silently ignore break; } } return into(glide.buildImageViewTarget(view, transcodeClass)); } /** * Returns a future that can be used to do a blocking get on a background thread. * * @param width The desired width in pixels (note this will be overriden by {@link #override(int, int)} if * previously called). * @param height The desired height in pixels (note this will be overriden by {@link #override(int, int)}} * if previously called). * * @see Glide#clear(com.bumptech.glide.request.FutureTarget) * * @return An {@link com.bumptech.glide.request.FutureTarget} that can be used to obtain the * resource in a blocking manner. */ public FutureTarget into(int width, int height) { final RequestFutureTarget target = new RequestFutureTarget(glide.getMainHandler(), width, height); // TODO: Currently all loads must be started on the main thread... glide.getMainHandler().post(new Runnable() { @Override public void run() { if (!target.isCancelled()) { into(target); } } }); return target; } /** * Preloads the resource into the cache using the given width and height. * *

* Pre-loading is useful for making sure that resources you are going to to want in the near future are * available quickly. *

* * @see com.bumptech.glide.ListPreloader */ public Target preload(int width, int height) { final PreloadTarget target = PreloadTarget.obtain(width, height); return into(target); } void applyCenterCrop() { // To be implemented by subclasses when possible. } void applyFitCenter() { // To be implemented by subclasses when possible. } private Priority getThumbnailPriority() { final Priority result; if (priority == Priority.LOW) { result = Priority.NORMAL; } else if (priority == Priority.NORMAL) { result = Priority.HIGH; } else { result = Priority.IMMEDIATE; } return result; } private Request buildRequest(Target target) { if (priority == null) { priority = Priority.NORMAL; } return buildRequestRecursive(target, null); } private Request buildRequestRecursive(Target target, ThumbnailRequestCoordinator parentCoordinator) { if (thumbnailRequestBuilder != null) { // Recursive case: contains a potentially recursive thumbnail request builder. if (thumbnailRequestBuilder.animationFactory.equals(NoAnimation.getFactory())) { thumbnailRequestBuilder.animationFactory = animationFactory; } if (thumbnailRequestBuilder.priority == null) { thumbnailRequestBuilder.priority = getThumbnailPriority(); } ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator); Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator); // Recursively generate thumbnail requests. Request thumbRequest = thumbnailRequestBuilder.buildRequestRecursive(target, coordinator); coordinator.setRequests(fullRequest, thumbRequest); return coordinator; } else if (thumbSizeMultiplier != null) { // Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse. ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator); Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator); Request thumbnailRequest = obtainRequest(target, thumbSizeMultiplier, getThumbnailPriority(), coordinator); coordinator.setRequests(fullRequest, thumbnailRequest); return coordinator; } else { // Base case: no thumbnail. return obtainRequest(target, sizeMultiplier, priority, parentCoordinator); } } private Request obtainRequest(Target target, float sizeMultiplier, Priority priority, RequestCoordinator requestCoordinator) { return GenericRequest.obtain( loadProvider, model, signature, context, priority, target, sizeMultiplier, placeholderDrawable, placeholderId, errorPlaceholder, errorId, requestListener, requestCoordinator, glide.getEngine(), transformation, transcodeClass, isCacheable, animationFactory, overrideWidth, overrideHeight, diskCacheStrategy); } }