/** * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.koushikdutta.ion; import android.content.Context; import android.text.TextUtils; import android.util.AttributeSet; import android.widget.ImageView; import com.koushikdutta.async.future.Future; import java.lang.ref.WeakReference; /** * Handles fetching an image from a URL as well as the life-cycle of the * associated request. */ public class NetworkImageView extends ImageView { /** The URL of the network image to load */ private String mUrl; /** * Resource ID of the image to be used as a placeholder until the network image is loaded. */ private int mDefaultImageId; /** * Resource ID of the image to be used if the network response fails. */ private int mErrorImageId; /** Local copy of the Ion. */ private Ion mIon; /** Current Future<Bitmap. (either in-flight or finished) */ private WeakReference<Future<ImageView>> mFuture; public NetworkImageView(Context context) { this(context, null); } public NetworkImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public NetworkImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } /** * Sets URL of the image that should be loaded into this view. Note that calling this will * immediately either set the cached image (if available) or the default image specified by * {@link NetworkImageView#setDefaultImageResId(int)} on the view. * * NOTE: If applicable, {@link NetworkImageView#setDefaultImageResId(int)} and * {@link NetworkImageView#setErrorImageResId(int)} should be called prior to calling * this function. * * @param url The URL that should be loaded into this ImageView. * @param ion ImageLoader that will be used to make the request. */ public void setImageUrl(String url, Ion ion) { mIon = ion; // The URL has potentially changed. See if we need to load it. loadImageIfNecessary(url); } /** * Sets URL of the image that should be loaded into this view. Note that calling this will * immediately either set the cached image (if available) or the default image specified by * {@link NetworkImageView#setDefaultImageResId(int)} on the view. * * NOTE: If applicable, {@link NetworkImageView#setDefaultImageResId(int)} and * {@link NetworkImageView#setErrorImageResId(int)} should be called prior to calling * this function. * * @param url The URL that should be loaded into this ImageView. */ public void setImageUrl(String url) { mIon = Ion.getDefault(getContext()); // The URL has potentially changed. See if we need to load it. loadImageIfNecessary(url); } /** * Sets the default image resource ID to be used for this view until the attempt to load it * completes. */ public void setDefaultImageResId(int defaultImage) { mDefaultImageId = defaultImage; } /** * Sets the error image resource ID to be used for this view in the event that the image * requested fails to load. */ public void setErrorImageResId(int errorImage) { mErrorImageId = errorImage; } /** * Loads the image for the view if it isn't already loaded. */ private void loadImageIfNecessary(String url) { int width = getWidth(); int height = getHeight(); String oldUrl = mUrl; mUrl = url; // if the view's bounds aren't known yet, hold off on loading the image. if (width == 0 && height == 0) { return; } Future<ImageView> future; if (mFuture == null) future = null; else future = mFuture.get(); // if the URL to be loaded in this view is empty, cancel any old requests and recycle the // currently loaded image. if (TextUtils.isEmpty(mUrl)) { if (future != null) { future.cancel(); mFuture = null; } setImageBitmap(null); return; } // if there was an old request in this view, check if it needs to be canceled. if (future != null && oldUrl != null) { if (oldUrl.equals(mUrl)) { // if the request is from the same URL, return. return; } else { // if there is a pre-existing request, cancel it if it's fetching a different URL. future.cancel(); setImageBitmap(null); } } // The pre-existing content of this view didn't match the current URL. Load the new image // from the network. future = mIon.build(this) .placeholder(mDefaultImageId) .error(mErrorImageId) .load(mUrl); mFuture = new WeakReference<Future<ImageView>>(future); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); loadImageIfNecessary(mUrl); } @Override protected void onDetachedFromWindow() { Future<ImageView> future; if (mFuture == null) future = null; else future = mFuture.get(); if (future != null) { // If the view was bound to an image request, cancel it and recycle // out the image from the view. future.cancel(); setImageBitmap(null); // also recycle out the container so we can reload the image if necessary. mFuture = null; } super.onDetachedFromWindow(); } @Override protected void drawableStateChanged() { super.drawableStateChanged(); invalidate(); } }