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 */ 16 package com.android.volley.toolbox; 17 18 import android.content.Context; 19 import android.text.TextUtils; 20 import android.util.AttributeSet; 21 import android.view.ViewGroup.LayoutParams; 22 import android.widget.ImageView; 23 24 import com.android.volley.VolleyError; 25 import com.android.volley.toolbox.ImageLoader.ImageContainer; 26 import 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 */ 32 public 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 NetworkImageView(Context context)52 public NetworkImageView(Context context) { 53 this(context, null); 54 } 55 NetworkImageView(Context context, AttributeSet attrs)56 public NetworkImageView(Context context, AttributeSet attrs) { 57 this(context, attrs, 0); 58 } 59 NetworkImageView(Context context, AttributeSet attrs, int defStyle)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 */ setImageUrl(String url, ImageLoader imageLoader)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 */ setDefaultImageResId(int defaultImage)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 */ setErrorImageResId(int errorImage)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 */ loadImageIfNecessary(final boolean isInLayoutPass)103 void loadImageIfNecessary(final boolean isInLayoutPass) { 104 int width = getWidth(); 105 int height = getHeight(); 106 ScaleType scaleType = getScaleType(); 107 108 boolean wrapWidth = false, wrapHeight = false; 109 if (getLayoutParams() != null) { 110 wrapWidth = getLayoutParams().width == LayoutParams.WRAP_CONTENT; 111 wrapHeight = getLayoutParams().height == LayoutParams.WRAP_CONTENT; 112 } 113 114 // if the view's bounds aren't known yet, and this is not a wrap-content/wrap-content 115 // view, hold off on loading the image. 116 boolean isFullyWrapContent = wrapWidth && wrapHeight; 117 if (width == 0 && height == 0 && !isFullyWrapContent) { 118 return; 119 } 120 121 // if the URL to be loaded in this view is empty, cancel any old requests and clear the 122 // currently loaded image. 123 if (TextUtils.isEmpty(mUrl)) { 124 if (mImageContainer != null) { 125 mImageContainer.cancelRequest(); 126 mImageContainer = null; 127 } 128 setDefaultImageOrNull(); 129 return; 130 } 131 132 // if there was an old request in this view, check if it needs to be canceled. 133 if (mImageContainer != null && mImageContainer.getRequestUrl() != null) { 134 if (mImageContainer.getRequestUrl().equals(mUrl)) { 135 // if the request is from the same URL, return. 136 return; 137 } else { 138 // if there is a pre-existing request, cancel it if it's fetching a different URL. 139 mImageContainer.cancelRequest(); 140 setDefaultImageOrNull(); 141 } 142 } 143 144 // Calculate the max image width / height to use while ignoring WRAP_CONTENT dimens. 145 int maxWidth = wrapWidth ? 0 : width; 146 int maxHeight = wrapHeight ? 0 : height; 147 148 // The pre-existing content of this view didn't match the current URL. Load the new image 149 // from the network. 150 ImageContainer newContainer = mImageLoader.get(mUrl, 151 new ImageListener() { 152 @Override 153 public void onErrorResponse(VolleyError error) { 154 if (mErrorImageId != 0) { 155 setImageResource(mErrorImageId); 156 } 157 } 158 159 @Override 160 public void onResponse(final ImageContainer response, boolean isImmediate) { 161 // If this was an immediate response that was delivered inside of a layout 162 // pass do not set the image immediately as it will trigger a requestLayout 163 // inside of a layout. Instead, defer setting the image by posting back to 164 // the main thread. 165 if (isImmediate && isInLayoutPass) { 166 post(new Runnable() { 167 @Override 168 public void run() { 169 onResponse(response, false); 170 } 171 }); 172 return; 173 } 174 175 if (response.getBitmap() != null) { 176 setImageBitmap(response.getBitmap()); 177 } else if (mDefaultImageId != 0) { 178 setImageResource(mDefaultImageId); 179 } 180 } 181 }, maxWidth, maxHeight, scaleType); 182 183 // update the ImageContainer to be the new bitmap container. 184 mImageContainer = newContainer; 185 } 186 setDefaultImageOrNull()187 private void setDefaultImageOrNull() { 188 if(mDefaultImageId != 0) { 189 setImageResource(mDefaultImageId); 190 } 191 else { 192 setImageBitmap(null); 193 } 194 } 195 196 @Override onLayout(boolean changed, int left, int top, int right, int bottom)197 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 198 super.onLayout(changed, left, top, right, bottom); 199 loadImageIfNecessary(true); 200 } 201 202 @Override onDetachedFromWindow()203 protected void onDetachedFromWindow() { 204 if (mImageContainer != null) { 205 // If the view was bound to an image request, cancel it and clear 206 // out the image from the view. 207 mImageContainer.cancelRequest(); 208 setImageBitmap(null); 209 // also clear out the container so we can reload the image if necessary. 210 mImageContainer = null; 211 } 212 super.onDetachedFromWindow(); 213 } 214 215 @Override drawableStateChanged()216 protected void drawableStateChanged() { 217 super.drawableStateChanged(); 218 invalidate(); 219 } 220 } 221