• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  * <p>http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * <p>Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11  * express or implied. See the License for the specific language governing permissions and
12  * limitations under the License.
13  */
14 package com.android.volley.toolbox;
15 
16 import android.content.Context;
17 import android.graphics.Bitmap;
18 import android.graphics.drawable.Drawable;
19 import android.text.TextUtils;
20 import android.util.AttributeSet;
21 import android.view.ViewGroup.LayoutParams;
22 import android.widget.ImageView;
23 import androidx.annotation.MainThread;
24 import androidx.annotation.Nullable;
25 import com.android.volley.VolleyError;
26 import com.android.volley.toolbox.ImageLoader.ImageContainer;
27 import com.android.volley.toolbox.ImageLoader.ImageListener;
28 
29 /** Handles fetching an image from a URL as well as the life-cycle of the associated request. */
30 public class NetworkImageView extends ImageView {
31     /** The URL of the network image to load */
32     private String mUrl;
33 
34     /**
35      * Resource ID of the image to be used as a placeholder until the network image is loaded. Won't
36      * be set at the same time as mDefaultImageDrawable or mDefaultImageBitmap.
37      */
38     private int mDefaultImageId;
39 
40     /**
41      * Drawable of the image to be used as a placeholder until the network image is loaded. Won't be
42      * set at the same time as mDefaultImageId or mDefaultImageBitmap.
43      */
44     @Nullable private Drawable mDefaultImageDrawable;
45 
46     /**
47      * Bitmap of the image to be used as a placeholder until the network image is loaded. Won't be
48      * set at the same time as mDefaultImageId or mDefaultImageDrawable.
49      */
50     @Nullable private Bitmap mDefaultImageBitmap;
51 
52     /**
53      * Resource ID of the image to be used if the network response fails. Won't be set at the same
54      * time as mErrorImageDrawable or mErrorImageBitmap.
55      */
56     private int mErrorImageId;
57 
58     /**
59      * Bitmap of the image to be used if the network response fails. Won't be set at the same time
60      * as mErrorImageId or mErrorImageBitmap.
61      */
62     @Nullable private Drawable mErrorImageDrawable;
63 
64     /**
65      * Bitmap of the image to be used if the network response fails. Won't be set at the same time
66      * as mErrorImageId or mErrorImageDrawable.
67      */
68     @Nullable private Bitmap mErrorImageBitmap;
69 
70     /** Local copy of the ImageLoader. */
71     private ImageLoader mImageLoader;
72 
73     /** Current ImageContainer. (either in-flight or finished) */
74     private ImageContainer mImageContainer;
75 
NetworkImageView(Context context)76     public NetworkImageView(Context context) {
77         this(context, null);
78     }
79 
NetworkImageView(Context context, AttributeSet attrs)80     public NetworkImageView(Context context, AttributeSet attrs) {
81         this(context, attrs, 0);
82     }
83 
NetworkImageView(Context context, AttributeSet attrs, int defStyle)84     public NetworkImageView(Context context, AttributeSet attrs, int defStyle) {
85         super(context, attrs, defStyle);
86     }
87 
88     /**
89      * Sets URL of the image that should be loaded into this view. Note that calling this will
90      * immediately either set the cached image (if available) or the default image specified by
91      * {@link NetworkImageView#setDefaultImageResId(int)} on the view.
92      *
93      * <p>NOTE: If applicable, {@link NetworkImageView#setDefaultImageResId(int)} or {@link
94      * NetworkImageView#setDefaultImageBitmap} and {@link NetworkImageView#setErrorImageResId(int)}
95      * or {@link NetworkImageView#setErrorImageBitmap(Bitmap)} should be called prior to calling
96      * this function.
97      *
98      * <p>Must be called from the main thread.
99      *
100      * @param url The URL that should be loaded into this ImageView.
101      * @param imageLoader ImageLoader that will be used to make the request.
102      */
103     @MainThread
setImageUrl(String url, ImageLoader imageLoader)104     public void setImageUrl(String url, ImageLoader imageLoader) {
105         Threads.throwIfNotOnMainThread();
106         mUrl = url;
107         mImageLoader = imageLoader;
108         // The URL has potentially changed. See if we need to load it.
109         loadImageIfNecessary(/* isInLayoutPass= */ false);
110     }
111 
112     /**
113      * Sets the default image resource ID to be used for this view until the attempt to load it
114      * completes.
115      *
116      * <p>This will clear anything set by {@link NetworkImageView#setDefaultImageBitmap} or {@link
117      * NetworkImageView#setDefaultImageDrawable}.
118      */
setDefaultImageResId(int defaultImage)119     public void setDefaultImageResId(int defaultImage) {
120         mDefaultImageBitmap = null;
121         mDefaultImageDrawable = null;
122         mDefaultImageId = defaultImage;
123     }
124 
125     /**
126      * Sets the default image drawable to be used for this view until the attempt to load it
127      * completes.
128      *
129      * <p>This will clear anything set by {@link NetworkImageView#setDefaultImageResId} or {@link
130      * NetworkImageView#setDefaultImageBitmap}.
131      */
setDefaultImageDrawable(@ullable Drawable defaultImageDrawable)132     public void setDefaultImageDrawable(@Nullable Drawable defaultImageDrawable) {
133         mDefaultImageId = 0;
134         mDefaultImageBitmap = null;
135         mDefaultImageDrawable = defaultImageDrawable;
136     }
137 
138     /**
139      * Sets the default image bitmap to be used for this view until the attempt to load it
140      * completes.
141      *
142      * <p>This will clear anything set by {@link NetworkImageView#setDefaultImageResId} or {@link
143      * NetworkImageView#setDefaultImageDrawable}.
144      */
setDefaultImageBitmap(Bitmap defaultImage)145     public void setDefaultImageBitmap(Bitmap defaultImage) {
146         mDefaultImageId = 0;
147         mDefaultImageDrawable = null;
148         mDefaultImageBitmap = defaultImage;
149     }
150 
151     /**
152      * Sets the error image resource ID to be used for this view in the event that the image
153      * requested fails to load.
154      *
155      * <p>This will clear anything set by {@link NetworkImageView#setErrorImageBitmap} or {@link
156      * NetworkImageView#setErrorImageDrawable}.
157      */
setErrorImageResId(int errorImage)158     public void setErrorImageResId(int errorImage) {
159         mErrorImageBitmap = null;
160         mErrorImageDrawable = null;
161         mErrorImageId = errorImage;
162     }
163 
164     /**
165      * Sets the error image drawable to be used for this view in the event that the image requested
166      * fails to load.
167      *
168      * <p>This will clear anything set by {@link NetworkImageView#setErrorImageResId} or {@link
169      * NetworkImageView#setDefaultImageBitmap}.
170      */
setErrorImageDrawable(@ullable Drawable errorImageDrawable)171     public void setErrorImageDrawable(@Nullable Drawable errorImageDrawable) {
172         mErrorImageId = 0;
173         mErrorImageBitmap = null;
174         mErrorImageDrawable = errorImageDrawable;
175     }
176 
177     /**
178      * Sets the error image bitmap to be used for this view in the event that the image requested
179      * fails to load.
180      *
181      * <p>This will clear anything set by {@link NetworkImageView#setErrorImageResId} or {@link
182      * NetworkImageView#setDefaultImageDrawable}.
183      */
setErrorImageBitmap(Bitmap errorImage)184     public void setErrorImageBitmap(Bitmap errorImage) {
185         mErrorImageId = 0;
186         mErrorImageDrawable = null;
187         mErrorImageBitmap = errorImage;
188     }
189 
190     /**
191      * Loads the image for the view if it isn't already loaded.
192      *
193      * @param isInLayoutPass True if this was invoked from a layout pass, false otherwise.
194      */
loadImageIfNecessary(final boolean isInLayoutPass)195     void loadImageIfNecessary(final boolean isInLayoutPass) {
196         int width = getWidth();
197         int height = getHeight();
198         ScaleType scaleType = getScaleType();
199 
200         boolean wrapWidth = false, wrapHeight = false;
201         if (getLayoutParams() != null) {
202             wrapWidth = getLayoutParams().width == LayoutParams.WRAP_CONTENT;
203             wrapHeight = getLayoutParams().height == LayoutParams.WRAP_CONTENT;
204         }
205 
206         // if the view's bounds aren't known yet, and this is not a wrap-content/wrap-content
207         // view, hold off on loading the image.
208         boolean isFullyWrapContent = wrapWidth && wrapHeight;
209         if (width == 0 && height == 0 && !isFullyWrapContent) {
210             return;
211         }
212 
213         // if the URL to be loaded in this view is empty, cancel any old requests and clear the
214         // currently loaded image.
215         if (TextUtils.isEmpty(mUrl)) {
216             if (mImageContainer != null) {
217                 mImageContainer.cancelRequest();
218                 mImageContainer = null;
219             }
220             setDefaultImageOrNull();
221             return;
222         }
223 
224         // if there was an old request in this view, check if it needs to be canceled.
225         if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
226             if (mImageContainer.getRequestUrl().equals(mUrl)) {
227                 // if the request is from the same URL, return.
228                 return;
229             } else {
230                 // if there is a pre-existing request, cancel it if it's fetching a different URL.
231                 mImageContainer.cancelRequest();
232                 setDefaultImageOrNull();
233             }
234         }
235 
236         // Calculate the max image width / height to use while ignoring WRAP_CONTENT dimens.
237         int maxWidth = wrapWidth ? 0 : width;
238         int maxHeight = wrapHeight ? 0 : height;
239 
240         // The pre-existing content of this view didn't match the current URL. Load the new image
241         // from the network.
242 
243         // update the ImageContainer to be the new bitmap container.
244         mImageContainer =
245                 mImageLoader.get(
246                         mUrl,
247                         new ImageListener() {
248                             @Override
249                             public void onErrorResponse(VolleyError error) {
250                                 if (mErrorImageId != 0) {
251                                     setImageResource(mErrorImageId);
252                                 } else if (mErrorImageDrawable != null) {
253                                     setImageDrawable(mErrorImageDrawable);
254                                 } else if (mErrorImageBitmap != null) {
255                                     setImageBitmap(mErrorImageBitmap);
256                                 }
257                             }
258 
259                             @Override
260                             public void onResponse(
261                                     final ImageContainer response, boolean isImmediate) {
262                                 // If this was an immediate response that was delivered inside of a
263                                 // layout
264                                 // pass do not set the image immediately as it will trigger a
265                                 // requestLayout
266                                 // inside of a layout. Instead, defer setting the image by posting
267                                 // back to
268                                 // the main thread.
269                                 if (isImmediate && isInLayoutPass) {
270                                     post(
271                                             new Runnable() {
272                                                 @Override
273                                                 public void run() {
274                                                     onResponse(response, /* isImmediate= */ false);
275                                                 }
276                                             });
277                                     return;
278                                 }
279 
280                                 if (response.getBitmap() != null) {
281                                     setImageBitmap(response.getBitmap());
282                                 } else if (mDefaultImageId != 0) {
283                                     setImageResource(mDefaultImageId);
284                                 } else if (mDefaultImageDrawable != null) {
285                                     setImageDrawable(mDefaultImageDrawable);
286                                 } else if (mDefaultImageBitmap != null) {
287                                     setImageBitmap(mDefaultImageBitmap);
288                                 }
289                             }
290                         },
291                         maxWidth,
292                         maxHeight,
293                         scaleType);
294     }
295 
setDefaultImageOrNull()296     private void setDefaultImageOrNull() {
297         if (mDefaultImageId != 0) {
298             setImageResource(mDefaultImageId);
299         } else if (mDefaultImageDrawable != null) {
300             setImageDrawable(mDefaultImageDrawable);
301         } else if (mDefaultImageBitmap != null) {
302             setImageBitmap(mDefaultImageBitmap);
303         } else {
304             setImageBitmap(null);
305         }
306     }
307 
308     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)309     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
310         super.onLayout(changed, left, top, right, bottom);
311         loadImageIfNecessary(/* isInLayoutPass= */ true);
312     }
313 
314     @Override
onDetachedFromWindow()315     protected void onDetachedFromWindow() {
316         if (mImageContainer != null) {
317             // If the view was bound to an image request, cancel it and clear
318             // out the image from the view.
319             mImageContainer.cancelRequest();
320             setImageBitmap(null);
321             // also clear out the container so we can reload the image if necessary.
322             mImageContainer = null;
323         }
324         super.onDetachedFromWindow();
325     }
326 
327     @Override
drawableStateChanged()328     protected void drawableStateChanged() {
329         super.drawableStateChanged();
330         invalidate();
331     }
332 }
333