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