Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
1 /*
2 * Copyright (C) 2013 Square, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.squareup.picasso;
18 import android.content.res.Resources;
19 import android.graphics.Bitmap;
20 import android.graphics.drawable.Drawable;
21 import android.net.Uri;
22 import android.widget.ImageView;
23 import java.io.IOException;
25 import static com.squareup.picasso.BitmapHunter.forRequest;
26 import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY;
27 import static com.squareup.picasso.Utils.checkNotMain;
28 import static com.squareup.picasso.Utils.createKey;
30 /** Fluent API for building an image download request. */
31 @SuppressWarnings("UnusedDeclaration") // Public API.
32 public class RequestCreator {
33 private final Picasso picasso;
34 private final Request.Builder data;
36 private boolean skipMemoryCache;
37 private boolean noFade;
38 private boolean deferred;
39 private int placeholderResId;
40 private Drawable placeholderDrawable;
41 private int errorResId;
42 private Drawable errorDrawable;
44 RequestCreator(Picasso picasso, Uri uri, int resourceId) {
45 if (picasso.shutdown) {
46 throw new IllegalStateException(
47 "Picasso instance already shut down. Cannot submit new requests.");
48 }
49 this.picasso = picasso;
50 this.data = new Request.Builder(uri, resourceId);
51 }
53 /**
54 * A placeholder drawable to be used while the image is being loaded. If the requested image is
55 * not immediately available in the memory cache then this resource will be set on the target
56 * {@link ImageView}.
57 */
58 public RequestCreator placeholder(int placeholderResId) {
59 if (placeholderResId == 0) {
60 throw new IllegalArgumentException("Placeholder image resource invalid.");
61 }
62 if (placeholderDrawable != null) {
63 throw new IllegalStateException("Placeholder image already set.");
64 }
65 this.placeholderResId = placeholderResId;
66 return this;
67 }
69 /**
70 * A placeholder drawable to be used while the image is being loaded. If the requested image is
71 * not immediately available in the memory cache then this resource will be set on the target
72 * {@link ImageView}.
73 * <p>
74 * If you are not using a placeholder image but want to clear an existing image (such as when
75 * used in an {@link android.widget.Adapter adapter}), pass in {@code null}.
76 */
77 public RequestCreator placeholder(Drawable placeholderDrawable) {
78 if (placeholderResId != 0) {
79 throw new IllegalStateException("Placeholder image already set.");
80 }
81 this.placeholderDrawable = placeholderDrawable;
82 return this;
83 }
85 /** An error drawable to be used if the request image could not be loaded. */
86 public RequestCreator error(int errorResId) {
87 if (errorResId == 0) {
88 throw new IllegalArgumentException("Error image resource invalid.");
89 }
90 if (errorDrawable != null) {
91 throw new IllegalStateException("Error image already set.");
92 }
93 this.errorResId = errorResId;
94 return this;
95 }
97 /** An error drawable to be used if the request image could not be loaded. */
98 public RequestCreator error(Drawable errorDrawable) {
99 if (errorDrawable == null) {
100 throw new IllegalArgumentException("Error image may not be null.");
101 }
102 if (errorResId != 0) {
103 throw new IllegalStateException("Error image already set.");
104 }
105 this.errorDrawable = errorDrawable;
106 return this;
107 }
109 /**
110 * Attempt to resize the image to fit exactly into the target {@link ImageView}'s bounds. This
111 * will result in delayed execution of the request until the {@link ImageView} has been measured.
112 * <p/>
113 * <em>Note:</em> This method works only when your target is an {@link ImageView}.
114 */
115 public RequestCreator fit() {
116 deferred = true;
117 return this;
118 }
120 /** Internal use only. Used by {@link DeferredRequestCreator}. */
121 RequestCreator unfit() {
122 deferred = false;
123 return this;
124 }
126 /** Resize the image to the specified dimension size. */
127 public RequestCreator resizeDimen(int targetWidthResId, int targetHeightResId) {
128 Resources resources = picasso.context.getResources();
129 int targetWidth = resources.getDimensionPixelSize(targetWidthResId);
130 int targetHeight = resources.getDimensionPixelSize(targetHeightResId);
131 return resize(targetWidth, targetHeight);
132 }
134 /** Resize the image to the specified size in pixels. */
135 public RequestCreator resize(int targetWidth, int targetHeight) {
136 data.resize(targetWidth, targetHeight);
137 return this;
138 }
140 /**
141 * Crops an image inside of the bounds specified by {@link #resize(int, int)} rather than
142 * distorting the aspect ratio. This cropping technique scales the image so that it fills the
143 * requested bounds and then crops the extra.
144 */
145 public RequestCreator centerCrop() {
146 data.centerCrop();
147 return this;
148 }
150 /**
151 * Centers an image inside of the bounds specified by {@link #resize(int, int)}. This scales
152 * the image so that both dimensions are equal to or less than the requested bounds.
153 */
154 public RequestCreator centerInside() {
155 data.centerInside();
156 return this;
157 }
159 /** Rotate the image by the specified degrees. */
160 public RequestCreator rotate(float degrees) {
161 data.rotate(degrees);
162 return this;
163 }
165 /** Rotate the image by the specified degrees around a pivot point. */
166 public RequestCreator rotate(float degrees, float pivotX, float pivotY) {
167 data.rotate(degrees, pivotX, pivotY);
168 return this;
169 }
171 /**
172 * Add a custom transformation to be applied to the image.
173 * <p/>
174 * Custom transformations will always be run after the built-in transformations.
175 */
176 // TODO show example of calling resize after a transform in the javadoc
177 public RequestCreator transform(Transformation transformation) {
178 data.transform(transformation);
179 return this;
180 }
182 /**
183 * Indicate that this action should not use the memory cache for attempting to load or save the
184 * image. This can be useful when you know an image will only ever be used once (e.g., loading
185 * an image from the filesystem and uploading to a remote server).
186 */
187 public RequestCreator skipMemoryCache() {
188 skipMemoryCache = true;
189 return this;
190 }
192 /** Disable brief fade in of images loaded from the disk cache or network. */
193 public RequestCreator noFade() {
194 noFade = true;
195 return this;
196 }
198 /** Synchronously fulfill this request. Must not be called from the main thread. */
199 public Bitmap get() throws IOException {
200 checkNotMain();
201 if (deferred) {
202 throw new IllegalStateException("Fit cannot be used with get.");
203 }
204 if (!data.hasImage()) {
205 return null;
206 }
208 Request finalData = picasso.transformRequest(data.build());
209 String key = createKey(finalData);
211 Action action = new GetAction(picasso, finalData, skipMemoryCache, key);
212 return forRequest(picasso.context, picasso, picasso.dispatcher, picasso.cache, picasso.stats,
213 action, picasso.dispatcher.downloader).hunt();
214 }
216 /**
217 * Asynchronously fulfills the request without a {@link ImageView} or {@link Target}. This is
218 * useful when you want to warm up the cache with an image.
219 */
220 public void fetch() {
221 if (deferred) {
222 throw new IllegalStateException("Fit cannot be used with fetch.");
223 }
224 if (data.hasImage()) {
225 Request finalData = picasso.transformRequest(data.build());
226 String key = createKey(finalData);
228 Action action = new FetchAction(picasso, finalData, skipMemoryCache, key);
229 picasso.enqueueAndSubmit(action);
230 }
231 }
233 /**
234 * Asynchronously fulfills the request into the specified {@link Target}. In most cases, you
235 * should use this when you are dealing with a custom {@link android.view.View View} or view
236 * holder which should implement the {@link Target} interface.
237 * <p>
238 * Implementing on a {@link android.view.View View}:
239 * <blockquote><pre>
240 * public class ProfileView extends FrameLayout implements Target {
241 * {@literal @}Override public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from) {
242 * setBackgroundDrawable(new BitmapDrawable(bitmap));
243 * }
244 *
245 * {@literal @}Override public void onBitmapFailed() {
246 * setBackgroundResource(R.drawable.profile_error);
247 * }
248 * }
249 * </pre></blockquote>
250 * Implementing on a view holder object for use inside of an adapter:
251 * <blockquote><pre>
252 * public class ViewHolder implements Target {
253 * public FrameLayout frame;
254 * public TextView name;
255 *
256 * {@literal @}Override public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from) {
257 * frame.setBackgroundDrawable(new BitmapDrawable(bitmap));
258 * }
259 *
260 * {@literal @}Override public void onBitmapFailed() {
261 * frame.setBackgroundResource(R.drawable.profile_error);
262 * }
263 * }
264 * </pre></blockquote>
265 * <p>
266 * <em>Note:</em> This method keeps a weak reference to the {@link Target} instance and will be
267 * garbage collected if you do not keep a strong reference to it. To receive callbacks when an
268 * image is loaded use {@link #into(android.widget.ImageView, Callback)}.
269 */
270 public void into(Target target) {
271 if (target == null) {
272 throw new IllegalArgumentException("Target must not be null.");
273 }
274 if (deferred) {
275 throw new IllegalStateException("Fit cannot be used with a Target.");
276 }
278 Drawable drawable =
279 placeholderResId != 0 ? picasso.context.getResources().getDrawable(placeholderResId)
280 : placeholderDrawable;
282 if (!data.hasImage()) {
283 picasso.cancelRequest(target);
284 target.onPrepareLoad(drawable);
285 return;
286 }
288 Request finalData = picasso.transformRequest(data.build());
289 String requestKey = createKey(finalData);
291 if (!skipMemoryCache) {
292 Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
293 if (bitmap != null) {
294 picasso.cancelRequest(target);
295 target.onBitmapLoaded(bitmap, MEMORY);
296 return;
297 }
298 }
300 target.onPrepareLoad(drawable);
302 Action action = new TargetAction(picasso, target, finalData, skipMemoryCache, requestKey);
303 picasso.enqueueAndSubmit(action);
304 }
306 /**
307 * Asynchronously fulfills the request into the specified {@link ImageView}.
308 * <p/>
309 * <em>Note:</em> This method keeps a weak reference to the {@link ImageView} instance and will
310 * automatically support object recycling.
311 */
312 public void into(ImageView target) {
313 into(target, null);
314 }
316 /**
317 * Asynchronously fulfills the request into the specified {@link ImageView} and invokes the
318 * target {@link Callback} if it's not {@code null}.
319 * <p/>
320 * <em>Note:</em> The {@link Callback} param is a strong reference and will prevent your
321 * {@link android.app.Activity} or {@link android.app.Fragment} from being garbage collected. If
322 * you use this method, it is <b>strongly</b> recommended you invoke an adjacent
323 * {@link Picasso#cancelRequest(android.widget.ImageView)} call to prevent temporary leaking.
324 */
325 public void into(ImageView target, Callback callback) {
326 if (target == null) {
327 throw new IllegalArgumentException("Target must not be null.");
328 }
330 if (!data.hasImage()) {
331 picasso.cancelRequest(target);
332 PicassoDrawable.setPlaceholder(target, placeholderResId, placeholderDrawable);
333 return;
334 }
336 if (deferred) {
337 if (data.hasSize()) {
338 throw new IllegalStateException("Fit cannot be used with resize.");
339 }
340 int measuredWidth = target.getMeasuredWidth();
341 int measuredHeight = target.getMeasuredHeight();
342 if (measuredWidth == 0 || measuredHeight == 0) {
343 PicassoDrawable.setPlaceholder(target, placeholderResId, placeholderDrawable);
344 picasso.defer(target, new DeferredRequestCreator(this, target, callback));
345 return;
346 }
347 data.resize(measuredWidth, measuredHeight);
348 }
350 Request finalData = picasso.transformRequest(data.build());
351 String requestKey = createKey(finalData);
353 if (!skipMemoryCache) {
354 Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
355 if (bitmap != null) {
356 picasso.cancelRequest(target);
357 PicassoDrawable.setBitmap(target, picasso.context, bitmap, MEMORY, noFade,
358 picasso.debugging);
359 if (callback != null) {
360 callback.onSuccess();
361 }
362 return;
363 }
364 }
366 PicassoDrawable.setPlaceholder(target, placeholderResId, placeholderDrawable);
368 Action action =
369 new ImageViewAction(picasso, target, finalData, skipMemoryCache, noFade, errorResId,
370 errorDrawable, requestKey, callback);
372 picasso.enqueueAndSubmit(action);
373 }
374 }