michael@0: /* michael@0: * Copyright (C) 2013 Square, Inc. michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: package com.squareup.picasso; michael@0: michael@0: import android.content.res.Resources; michael@0: import android.graphics.Bitmap; michael@0: import android.graphics.drawable.Drawable; michael@0: import android.net.Uri; michael@0: import android.widget.ImageView; michael@0: import java.io.IOException; michael@0: michael@0: import static com.squareup.picasso.BitmapHunter.forRequest; michael@0: import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY; michael@0: import static com.squareup.picasso.Utils.checkNotMain; michael@0: import static com.squareup.picasso.Utils.createKey; michael@0: michael@0: /** Fluent API for building an image download request. */ michael@0: @SuppressWarnings("UnusedDeclaration") // Public API. michael@0: public class RequestCreator { michael@0: private final Picasso picasso; michael@0: private final Request.Builder data; michael@0: michael@0: private boolean skipMemoryCache; michael@0: private boolean noFade; michael@0: private boolean deferred; michael@0: private int placeholderResId; michael@0: private Drawable placeholderDrawable; michael@0: private int errorResId; michael@0: private Drawable errorDrawable; michael@0: michael@0: RequestCreator(Picasso picasso, Uri uri, int resourceId) { michael@0: if (picasso.shutdown) { michael@0: throw new IllegalStateException( michael@0: "Picasso instance already shut down. Cannot submit new requests."); michael@0: } michael@0: this.picasso = picasso; michael@0: this.data = new Request.Builder(uri, resourceId); michael@0: } michael@0: michael@0: /** michael@0: * A placeholder drawable to be used while the image is being loaded. If the requested image is michael@0: * not immediately available in the memory cache then this resource will be set on the target michael@0: * {@link ImageView}. michael@0: */ michael@0: public RequestCreator placeholder(int placeholderResId) { michael@0: if (placeholderResId == 0) { michael@0: throw new IllegalArgumentException("Placeholder image resource invalid."); michael@0: } michael@0: if (placeholderDrawable != null) { michael@0: throw new IllegalStateException("Placeholder image already set."); michael@0: } michael@0: this.placeholderResId = placeholderResId; michael@0: return this; michael@0: } michael@0: michael@0: /** michael@0: * A placeholder drawable to be used while the image is being loaded. If the requested image is michael@0: * not immediately available in the memory cache then this resource will be set on the target michael@0: * {@link ImageView}. michael@0: *
michael@0: * If you are not using a placeholder image but want to clear an existing image (such as when michael@0: * used in an {@link android.widget.Adapter adapter}), pass in {@code null}. michael@0: */ michael@0: public RequestCreator placeholder(Drawable placeholderDrawable) { michael@0: if (placeholderResId != 0) { michael@0: throw new IllegalStateException("Placeholder image already set."); michael@0: } michael@0: this.placeholderDrawable = placeholderDrawable; michael@0: return this; michael@0: } michael@0: michael@0: /** An error drawable to be used if the request image could not be loaded. */ michael@0: public RequestCreator error(int errorResId) { michael@0: if (errorResId == 0) { michael@0: throw new IllegalArgumentException("Error image resource invalid."); michael@0: } michael@0: if (errorDrawable != null) { michael@0: throw new IllegalStateException("Error image already set."); michael@0: } michael@0: this.errorResId = errorResId; michael@0: return this; michael@0: } michael@0: michael@0: /** An error drawable to be used if the request image could not be loaded. */ michael@0: public RequestCreator error(Drawable errorDrawable) { michael@0: if (errorDrawable == null) { michael@0: throw new IllegalArgumentException("Error image may not be null."); michael@0: } michael@0: if (errorResId != 0) { michael@0: throw new IllegalStateException("Error image already set."); michael@0: } michael@0: this.errorDrawable = errorDrawable; michael@0: return this; michael@0: } michael@0: michael@0: /** michael@0: * Attempt to resize the image to fit exactly into the target {@link ImageView}'s bounds. This michael@0: * will result in delayed execution of the request until the {@link ImageView} has been measured. michael@0: *
michael@0: * Note: This method works only when your target is an {@link ImageView}. michael@0: */ michael@0: public RequestCreator fit() { michael@0: deferred = true; michael@0: return this; michael@0: } michael@0: michael@0: /** Internal use only. Used by {@link DeferredRequestCreator}. */ michael@0: RequestCreator unfit() { michael@0: deferred = false; michael@0: return this; michael@0: } michael@0: michael@0: /** Resize the image to the specified dimension size. */ michael@0: public RequestCreator resizeDimen(int targetWidthResId, int targetHeightResId) { michael@0: Resources resources = picasso.context.getResources(); michael@0: int targetWidth = resources.getDimensionPixelSize(targetWidthResId); michael@0: int targetHeight = resources.getDimensionPixelSize(targetHeightResId); michael@0: return resize(targetWidth, targetHeight); michael@0: } michael@0: michael@0: /** Resize the image to the specified size in pixels. */ michael@0: public RequestCreator resize(int targetWidth, int targetHeight) { michael@0: data.resize(targetWidth, targetHeight); michael@0: return this; michael@0: } michael@0: michael@0: /** michael@0: * Crops an image inside of the bounds specified by {@link #resize(int, int)} rather than michael@0: * distorting the aspect ratio. This cropping technique scales the image so that it fills the michael@0: * requested bounds and then crops the extra. michael@0: */ michael@0: public RequestCreator centerCrop() { michael@0: data.centerCrop(); michael@0: return this; michael@0: } michael@0: michael@0: /** michael@0: * Centers an image inside of the bounds specified by {@link #resize(int, int)}. This scales michael@0: * the image so that both dimensions are equal to or less than the requested bounds. michael@0: */ michael@0: public RequestCreator centerInside() { michael@0: data.centerInside(); michael@0: return this; michael@0: } michael@0: michael@0: /** Rotate the image by the specified degrees. */ michael@0: public RequestCreator rotate(float degrees) { michael@0: data.rotate(degrees); michael@0: return this; michael@0: } michael@0: michael@0: /** Rotate the image by the specified degrees around a pivot point. */ michael@0: public RequestCreator rotate(float degrees, float pivotX, float pivotY) { michael@0: data.rotate(degrees, pivotX, pivotY); michael@0: return this; michael@0: } michael@0: michael@0: /** michael@0: * Add a custom transformation to be applied to the image. michael@0: * michael@0: * Custom transformations will always be run after the built-in transformations. michael@0: */ michael@0: // TODO show example of calling resize after a transform in the javadoc michael@0: public RequestCreator transform(Transformation transformation) { michael@0: data.transform(transformation); michael@0: return this; michael@0: } michael@0: michael@0: /** michael@0: * Indicate that this action should not use the memory cache for attempting to load or save the michael@0: * image. This can be useful when you know an image will only ever be used once (e.g., loading michael@0: * an image from the filesystem and uploading to a remote server). michael@0: */ michael@0: public RequestCreator skipMemoryCache() { michael@0: skipMemoryCache = true; michael@0: return this; michael@0: } michael@0: michael@0: /** Disable brief fade in of images loaded from the disk cache or network. */ michael@0: public RequestCreator noFade() { michael@0: noFade = true; michael@0: return this; michael@0: } michael@0: michael@0: /** Synchronously fulfill this request. Must not be called from the main thread. */ michael@0: public Bitmap get() throws IOException { michael@0: checkNotMain(); michael@0: if (deferred) { michael@0: throw new IllegalStateException("Fit cannot be used with get."); michael@0: } michael@0: if (!data.hasImage()) { michael@0: return null; michael@0: } michael@0: michael@0: Request finalData = picasso.transformRequest(data.build()); michael@0: String key = createKey(finalData); michael@0: michael@0: Action action = new GetAction(picasso, finalData, skipMemoryCache, key); michael@0: return forRequest(picasso.context, picasso, picasso.dispatcher, picasso.cache, picasso.stats, michael@0: action, picasso.dispatcher.downloader).hunt(); michael@0: } michael@0: michael@0: /** michael@0: * Asynchronously fulfills the request without a {@link ImageView} or {@link Target}. This is michael@0: * useful when you want to warm up the cache with an image. michael@0: */ michael@0: public void fetch() { michael@0: if (deferred) { michael@0: throw new IllegalStateException("Fit cannot be used with fetch."); michael@0: } michael@0: if (data.hasImage()) { michael@0: Request finalData = picasso.transformRequest(data.build()); michael@0: String key = createKey(finalData); michael@0: michael@0: Action action = new FetchAction(picasso, finalData, skipMemoryCache, key); michael@0: picasso.enqueueAndSubmit(action); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Asynchronously fulfills the request into the specified {@link Target}. In most cases, you michael@0: * should use this when you are dealing with a custom {@link android.view.View View} or view michael@0: * holder which should implement the {@link Target} interface. michael@0: *michael@0: * Implementing on a {@link android.view.View View}: michael@0: *
michael@0: * Implementing on a view holder object for use inside of an adapter: michael@0: *michael@0: * public class ProfileView extends FrameLayout implements Target { michael@0: * {@literal @}Override public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from) { michael@0: * setBackgroundDrawable(new BitmapDrawable(bitmap)); michael@0: * } michael@0: * michael@0: * {@literal @}Override public void onBitmapFailed() { michael@0: * setBackgroundResource(R.drawable.profile_error); michael@0: * } michael@0: * } michael@0: *
michael@0: *michael@0: * public class ViewHolder implements Target { michael@0: * public FrameLayout frame; michael@0: * public TextView name; michael@0: * michael@0: * {@literal @}Override public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from) { michael@0: * frame.setBackgroundDrawable(new BitmapDrawable(bitmap)); michael@0: * } michael@0: * michael@0: * {@literal @}Override public void onBitmapFailed() { michael@0: * frame.setBackgroundResource(R.drawable.profile_error); michael@0: * } michael@0: * } michael@0: *
michael@0: * Note: This method keeps a weak reference to the {@link Target} instance and will be michael@0: * garbage collected if you do not keep a strong reference to it. To receive callbacks when an michael@0: * image is loaded use {@link #into(android.widget.ImageView, Callback)}. michael@0: */ michael@0: public void into(Target target) { michael@0: if (target == null) { michael@0: throw new IllegalArgumentException("Target must not be null."); michael@0: } michael@0: if (deferred) { michael@0: throw new IllegalStateException("Fit cannot be used with a Target."); michael@0: } michael@0: michael@0: Drawable drawable = michael@0: placeholderResId != 0 ? picasso.context.getResources().getDrawable(placeholderResId) michael@0: : placeholderDrawable; michael@0: michael@0: if (!data.hasImage()) { michael@0: picasso.cancelRequest(target); michael@0: target.onPrepareLoad(drawable); michael@0: return; michael@0: } michael@0: michael@0: Request finalData = picasso.transformRequest(data.build()); michael@0: String requestKey = createKey(finalData); michael@0: michael@0: if (!skipMemoryCache) { michael@0: Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey); michael@0: if (bitmap != null) { michael@0: picasso.cancelRequest(target); michael@0: target.onBitmapLoaded(bitmap, MEMORY); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: target.onPrepareLoad(drawable); michael@0: michael@0: Action action = new TargetAction(picasso, target, finalData, skipMemoryCache, requestKey); michael@0: picasso.enqueueAndSubmit(action); michael@0: } michael@0: michael@0: /** michael@0: * Asynchronously fulfills the request into the specified {@link ImageView}. michael@0: *
michael@0: * Note: This method keeps a weak reference to the {@link ImageView} instance and will michael@0: * automatically support object recycling. michael@0: */ michael@0: public void into(ImageView target) { michael@0: into(target, null); michael@0: } michael@0: michael@0: /** michael@0: * Asynchronously fulfills the request into the specified {@link ImageView} and invokes the michael@0: * target {@link Callback} if it's not {@code null}. michael@0: * michael@0: * Note: The {@link Callback} param is a strong reference and will prevent your michael@0: * {@link android.app.Activity} or {@link android.app.Fragment} from being garbage collected. If michael@0: * you use this method, it is strongly recommended you invoke an adjacent michael@0: * {@link Picasso#cancelRequest(android.widget.ImageView)} call to prevent temporary leaking. michael@0: */ michael@0: public void into(ImageView target, Callback callback) { michael@0: if (target == null) { michael@0: throw new IllegalArgumentException("Target must not be null."); michael@0: } michael@0: michael@0: if (!data.hasImage()) { michael@0: picasso.cancelRequest(target); michael@0: PicassoDrawable.setPlaceholder(target, placeholderResId, placeholderDrawable); michael@0: return; michael@0: } michael@0: michael@0: if (deferred) { michael@0: if (data.hasSize()) { michael@0: throw new IllegalStateException("Fit cannot be used with resize."); michael@0: } michael@0: int measuredWidth = target.getMeasuredWidth(); michael@0: int measuredHeight = target.getMeasuredHeight(); michael@0: if (measuredWidth == 0 || measuredHeight == 0) { michael@0: PicassoDrawable.setPlaceholder(target, placeholderResId, placeholderDrawable); michael@0: picasso.defer(target, new DeferredRequestCreator(this, target, callback)); michael@0: return; michael@0: } michael@0: data.resize(measuredWidth, measuredHeight); michael@0: } michael@0: michael@0: Request finalData = picasso.transformRequest(data.build()); michael@0: String requestKey = createKey(finalData); michael@0: michael@0: if (!skipMemoryCache) { michael@0: Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey); michael@0: if (bitmap != null) { michael@0: picasso.cancelRequest(target); michael@0: PicassoDrawable.setBitmap(target, picasso.context, bitmap, MEMORY, noFade, michael@0: picasso.debugging); michael@0: if (callback != null) { michael@0: callback.onSuccess(); michael@0: } michael@0: return; michael@0: } michael@0: } michael@0: michael@0: PicassoDrawable.setPlaceholder(target, placeholderResId, placeholderDrawable); michael@0: michael@0: Action action = michael@0: new ImageViewAction(picasso, target, finalData, skipMemoryCache, noFade, errorResId, michael@0: errorDrawable, requestKey, callback); michael@0: michael@0: picasso.enqueueAndSubmit(action); michael@0: } michael@0: }