|
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; |
|
17 |
|
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; |
|
24 |
|
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; |
|
29 |
|
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; |
|
35 |
|
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; |
|
43 |
|
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 } |
|
52 |
|
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 } |
|
68 |
|
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 } |
|
84 |
|
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 } |
|
96 |
|
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 } |
|
108 |
|
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 } |
|
119 |
|
120 /** Internal use only. Used by {@link DeferredRequestCreator}. */ |
|
121 RequestCreator unfit() { |
|
122 deferred = false; |
|
123 return this; |
|
124 } |
|
125 |
|
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 } |
|
133 |
|
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 } |
|
139 |
|
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 } |
|
149 |
|
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 } |
|
158 |
|
159 /** Rotate the image by the specified degrees. */ |
|
160 public RequestCreator rotate(float degrees) { |
|
161 data.rotate(degrees); |
|
162 return this; |
|
163 } |
|
164 |
|
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 } |
|
170 |
|
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 } |
|
181 |
|
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 } |
|
191 |
|
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 } |
|
197 |
|
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 } |
|
207 |
|
208 Request finalData = picasso.transformRequest(data.build()); |
|
209 String key = createKey(finalData); |
|
210 |
|
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 } |
|
215 |
|
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); |
|
227 |
|
228 Action action = new FetchAction(picasso, finalData, skipMemoryCache, key); |
|
229 picasso.enqueueAndSubmit(action); |
|
230 } |
|
231 } |
|
232 |
|
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 } |
|
277 |
|
278 Drawable drawable = |
|
279 placeholderResId != 0 ? picasso.context.getResources().getDrawable(placeholderResId) |
|
280 : placeholderDrawable; |
|
281 |
|
282 if (!data.hasImage()) { |
|
283 picasso.cancelRequest(target); |
|
284 target.onPrepareLoad(drawable); |
|
285 return; |
|
286 } |
|
287 |
|
288 Request finalData = picasso.transformRequest(data.build()); |
|
289 String requestKey = createKey(finalData); |
|
290 |
|
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 } |
|
299 |
|
300 target.onPrepareLoad(drawable); |
|
301 |
|
302 Action action = new TargetAction(picasso, target, finalData, skipMemoryCache, requestKey); |
|
303 picasso.enqueueAndSubmit(action); |
|
304 } |
|
305 |
|
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 } |
|
315 |
|
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 } |
|
329 |
|
330 if (!data.hasImage()) { |
|
331 picasso.cancelRequest(target); |
|
332 PicassoDrawable.setPlaceholder(target, placeholderResId, placeholderDrawable); |
|
333 return; |
|
334 } |
|
335 |
|
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 } |
|
349 |
|
350 Request finalData = picasso.transformRequest(data.build()); |
|
351 String requestKey = createKey(finalData); |
|
352 |
|
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 } |
|
365 |
|
366 PicassoDrawable.setPlaceholder(target, placeholderResId, placeholderDrawable); |
|
367 |
|
368 Action action = |
|
369 new ImageViewAction(picasso, target, finalData, skipMemoryCache, noFade, errorResId, |
|
370 errorDrawable, requestKey, callback); |
|
371 |
|
372 picasso.enqueueAndSubmit(action); |
|
373 } |
|
374 } |