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         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 
    187     private void setDefaultImageOrNull() {
    188         if(mDefaultImageId != 0) {
    189             setImageResource(mDefaultImageId);
    190         }
    191         else {
    192             setImageBitmap(null);
    193         }
    194     }
    195 
    196     @Override
    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
    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
    216     protected void drawableStateChanged() {
    217         super.drawableStateChanged();
    218         invalidate();
    219     }
    220 }
    221