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