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