NetworkImageView.java revision 54086f9c87af47015cb5de5356310598e4b7dbf5
1/**
2 * Copyright (C) 2013 The Android Open Source Project
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 */
16package com.android.volley.toolbox;
17
18import android.content.Context;
19import android.text.TextUtils;
20import android.util.AttributeSet;
21import android.view.ViewGroup.LayoutParams;
22import android.widget.ImageView;
23
24import com.android.volley.VolleyError;
25import com.android.volley.toolbox.ImageLoader.ImageContainer;
26import com.android.volley.toolbox.ImageLoader.ImageListener;
27
28/**
29 * Handles fetching an image from a URL as well as the life-cycle of the
30 * associated request.
31 */
32public class NetworkImageView extends ImageView {
33    /** The URL of the network image to load */
34    private String mUrl;
35
36    /**
37     * Resource ID of the image to be used as a placeholder until the network image is loaded.
38     */
39    private int mDefaultImageId;
40
41    /**
42     * Resource ID of the image to be used if the network response fails.
43     */
44    private int mErrorImageId;
45
46    /** Local copy of the ImageLoader. */
47    private ImageLoader mImageLoader;
48
49    /** Current ImageContainer. (either in-flight or finished) */
50    private ImageContainer mImageContainer;
51
52    public NetworkImageView(Context context) {
53        this(context, null);
54    }
55
56    public NetworkImageView(Context context, AttributeSet attrs) {
57        this(context, attrs, 0);
58    }
59
60    public NetworkImageView(Context context, AttributeSet attrs, int defStyle) {
61        super(context, attrs, defStyle);
62    }
63
64    /**
65     * Sets URL of the image that should be loaded into this view. Note that calling this will
66     * immediately either set the cached image (if available) or the default image specified by
67     * {@link NetworkImageView#setDefaultImageResId(int)} on the view.
68     *
69     * NOTE: If applicable, {@link NetworkImageView#setDefaultImageResId(int)} and
70     * {@link NetworkImageView#setErrorImageResId(int)} should be called prior to calling
71     * this function.
72     *
73     * @param url The URL that should be loaded into this ImageView.
74     * @param imageLoader ImageLoader that will be used to make the request.
75     */
76    public void setImageUrl(String url, ImageLoader imageLoader) {
77        mUrl = url;
78        mImageLoader = imageLoader;
79        // The URL has potentially changed. See if we need to load it.
80        loadImageIfNecessary(false);
81    }
82
83    /**
84     * Sets the default image resource ID to be used for this view until the attempt to load it
85     * completes.
86     */
87    public void setDefaultImageResId(int defaultImage) {
88        mDefaultImageId = defaultImage;
89    }
90
91    /**
92     * Sets the error image resource ID to be used for this view in the event that the image
93     * requested fails to load.
94     */
95    public void setErrorImageResId(int errorImage) {
96        mErrorImageId = errorImage;
97    }
98
99    /**
100     * Loads the image for the view if it isn't already loaded.
101     * @param isInLayoutPass True if this was invoked from a layout pass, false otherwise.
102     */
103    private void loadImageIfNecessary(final boolean isInLayoutPass) {
104        int width = getWidth();
105        int height = getHeight();
106
107        boolean isFullyWrapContent = getLayoutParams() != null
108                && getLayoutParams().height == LayoutParams.WRAP_CONTENT
109                && getLayoutParams().width == LayoutParams.WRAP_CONTENT;
110        // if the view's bounds aren't known yet, and this is not a wrap-content/wrap-content
111        // view, hold off on loading the image.
112        if (width == 0 && height == 0 && !isFullyWrapContent) {
113            return;
114        }
115
116        // if the URL to be loaded in this view is empty, cancel any old requests and clear the
117        // currently loaded image.
118        if (TextUtils.isEmpty(mUrl)) {
119            if (mImageContainer != null) {
120                mImageContainer.cancelRequest();
121                mImageContainer = null;
122            }
123            setDefaultImageOrNull();
124            return;
125        }
126
127        // if there was an old request in this view, check if it needs to be canceled.
128        if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
129            if (mImageContainer.getRequestUrl().equals(mUrl)) {
130                // if the request is from the same URL, return.
131                return;
132            } else {
133                // if there is a pre-existing request, cancel it if it's fetching a different URL.
134                mImageContainer.cancelRequest();
135                setDefaultImageOrNull();
136            }
137        }
138
139        // The pre-existing content of this view didn't match the current URL. Load the new image
140        // from the network.
141        ImageContainer newContainer = mImageLoader.get(mUrl,
142                new ImageListener() {
143                    @Override
144                    public void onErrorResponse(VolleyError error) {
145                        if (mErrorImageId != 0) {
146                            setImageResource(mErrorImageId);
147                        }
148                    }
149
150                    @Override
151                    public void onResponse(final ImageContainer response, boolean isImmediate) {
152                        // If this was an immediate response that was delivered inside of a layout
153                        // pass do not set the image immediately as it will trigger a requestLayout
154                        // inside of a layout. Instead, defer setting the image by posting back to
155                        // the main thread.
156                        if (isImmediate && isInLayoutPass) {
157                            post(new Runnable() {
158                                @Override
159                                public void run() {
160                                    onResponse(response, false);
161                                }
162                            });
163                            return;
164                        }
165
166                        if (response.getBitmap() != null) {
167                            setImageBitmap(response.getBitmap());
168                        } else if (mDefaultImageId != 0) {
169                            setImageResource(mDefaultImageId);
170                        }
171                    }
172                });
173
174        // update the ImageContainer to be the new bitmap container.
175        mImageContainer = newContainer;
176    }
177
178    private void setDefaultImageOrNull() {
179        if(mDefaultImageId != 0) {
180            setImageResource(mDefaultImageId);
181        }
182        else {
183            setImageBitmap(null);
184        }
185    }
186
187    @Override
188    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
189        super.onLayout(changed, left, top, right, bottom);
190        loadImageIfNecessary(true);
191    }
192
193    @Override
194    protected void onDetachedFromWindow() {
195        if (mImageContainer != null) {
196            // If the view was bound to an image request, cancel it and clear
197            // out the image from the view.
198            mImageContainer.cancelRequest();
199            setImageBitmap(null);
200            // also clear out the container so we can reload the image if necessary.
201            mImageContainer = null;
202        }
203        super.onDetachedFromWindow();
204    }
205
206    @Override
207    protected void drawableStateChanged() {
208        super.drawableStateChanged();
209        invalidate();
210    }
211}
212