blob: eaf394004badd1e3bbf93a2fac31bb60066d395a [file] [log] [blame]
/*
* Copyright (C) 2012 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.example.android.threadsample;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import java.lang.ref.WeakReference;
import java.net.URL;
/**
* This class extends the standard Android ImageView View class with some features
* that are useful for downloading, decoding, and displaying Picasa images.
*
*/
public class PhotoView extends ImageView {
// Indicates if caching should be used
private boolean mCacheFlag;
// Status flag that indicates if onDraw has completed
private boolean mIsDrawn;
/*
* Creates a weak reference to the ImageView in this object. The weak
* reference prevents memory leaks and crashes, because it automatically tracks the "state" of
* the variable it backs. If the reference becomes invalid, the weak reference is garbage-
* collected.
* This technique is important for referring to objects that are part of a component lifecycle.
* Using a hard reference may cause memory leaks as the value continues to change; even worse,
* it can cause crashes if the underlying component is destroyed. Using a weak reference to
* a View ensures that the reference is more transitory in nature.
*/
private WeakReference<View> mThisView;
// Contains the ID of the internal View
private int mHideShowResId = -1;
// The URL that points to the source of the image for this ImageView
private URL mImageURL;
// The Thread that will be used to download the image for this ImageView
private PhotoTask mDownloadThread;
/**
* Creates an ImageDownloadView with no settings
* @param context A context for the View
*/
public PhotoView(Context context) {
super(context);
}
/**
* Creates an ImageDownloadView and gets attribute values
* @param context A Context to use with the View
* @param attributeSet The entire set of attributes for the View
*/
public PhotoView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
// Gets attributes associated with the attribute set
getAttributes(attributeSet);
}
/**
* Creates an ImageDownloadView, gets attribute values, and applies a default style
* @param context A context for the View
* @param attributeSet The entire set of attributes for the View
* @param defaultStyle The default style to use with the View
*/
public PhotoView(Context context, AttributeSet attributeSet, int defaultStyle) {
super(context, attributeSet, defaultStyle);
// Gets attributes associated with the attribute set
getAttributes(attributeSet);
}
/**
* Gets the resource ID for the hideShowSibling resource
* @param attributeSet The entire set of attributes for the View
*/
private void getAttributes(AttributeSet attributeSet) {
// Gets an array of attributes for the View
TypedArray attributes =
getContext().obtainStyledAttributes(attributeSet, R.styleable.ImageDownloaderView);
// Gets the resource Id of the View to hide or show
mHideShowResId =
attributes.getResourceId(R.styleable.ImageDownloaderView_hideShowSibling, -1);
// Returns the array for re-use
attributes.recycle();
}
/**
* Sets the visibility of the PhotoView
* @param visState The visibility state (see View.setVisibility)
*/
private void showView(int visState) {
// If the View contains something
if (mThisView != null) {
// Gets a local hard reference to the View
View localView = mThisView.get();
// If the weak reference actually contains something, set the visibility
if (localView != null)
localView.setVisibility(visState);
}
}
/**
* Sets the image in this ImageView to null, and makes the View visible
*/
public void clearImage() {
setImageDrawable(null);
showView(View.VISIBLE);
}
/**
* Returns the URL of the picture associated with this ImageView
* @return a URL
*/
final URL getLocation() {
return mImageURL;
}
/*
* This callback is invoked when the system attaches the ImageView to a Window. The callback
* is invoked before onDraw(), but may be invoked after onMeasure()
*/
@Override
protected void onAttachedToWindow() {
// Always call the supermethod first
super.onAttachedToWindow();
// If the sibling View is set and the parent of the ImageView is itself a View
if ((this.mHideShowResId != -1) && ((getParent() instanceof View))) {
// Gets a handle to the sibling View
View localView = ((View) getParent()).findViewById(this.mHideShowResId);
// If the sibling View contains something, make it the weak reference for this View
if (localView != null) {
this.mThisView = new WeakReference<View>(localView);
}
}
}
/*
* This callback is invoked when the ImageView is removed from a Window. It "unsets" variables
* to prevent memory leaks.
*/
@Override
protected void onDetachedFromWindow() {
// Clears out the image drawable, turns off the cache, disconnects the view from a URL
setImageURL(null, false, null);
// Gets the current Drawable, or null if no Drawable is attached
Drawable localDrawable = getDrawable();
// if the Drawable is null, unbind it from this VIew
if (localDrawable != null)
localDrawable.setCallback(null);
// If this View still exists, clears the weak reference, then sets the reference to null
if (mThisView != null) {
mThisView.clear();
mThisView = null;
}
// Sets the downloader thread to null
this.mDownloadThread = null;
// Always call the super method last
super.onDetachedFromWindow();
}
/*
* This callback is invoked when the system tells the View to draw itself. If the View isn't
* already drawn, and its URL isn't null, it invokes a Thread to download the image. Otherwise,
* it simply passes the existing Canvas to the super method
*/
@Override
protected void onDraw(Canvas canvas) {
// If the image isn't already drawn, and the URL is set
if ((!mIsDrawn) && (mImageURL != null)) {
// Starts downloading this View, using the current cache setting
mDownloadThread = PhotoManager.startDownload(this, mCacheFlag);
// After successfully downloading the image, this marks that it's available.
mIsDrawn = true;
}
// Always call the super method last
super.onDraw(canvas);
}
/**
* Sets the current View weak reference to be the incoming View. See the definition of
* mThisView
* @param view the View to use as the new WeakReference
*/
public void setHideView(View view) {
this.mThisView = new WeakReference<View>(view);
}
@Override
public void setImageBitmap(Bitmap paramBitmap) {
super.setImageBitmap(paramBitmap);
}
@Override
public void setImageDrawable(Drawable drawable) {
// The visibility of the View
int viewState;
/*
* Sets the View state to visible if the method is called with a null argument (the
* image is being cleared). Otherwise, sets the View state to invisible before refreshing
* it.
*/
if (drawable == null) {
viewState = View.VISIBLE;
} else {
viewState = View.INVISIBLE;
}
// Either hides or shows the View, depending on the view state
showView(viewState);
// Invokes the supermethod with the provided drawable
super.setImageDrawable(drawable);
}
/*
* Displays a drawable in the View
*/
@Override
public void setImageResource(int resId) {
super.setImageResource(resId);
}
/*
* Sets the URI for the Image
*/
@Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
}
/**
* Attempts to set the picture URL for this ImageView and then download the picture.
* <p>
* If the picture URL for this view is already set, and the input URL is not the same as the
* stored URL, then the picture has moved and any existing downloads are stopped.
* <p>
* If the input URL is the same as the stored URL, then nothing needs to be done.
* <p>
* If the stored URL is null, then this method starts a download and decode of the picture
* @param pictureURL An incoming URL for a Picasa picture
* @param cacheFlag Whether to use caching when doing downloading and decoding
* @param imageDrawable The Drawable to use for this ImageView
*/
public void setImageURL(URL pictureURL, boolean cacheFlag, Drawable imageDrawable) {
// If the picture URL for this ImageView is already set
if (mImageURL != null) {
// If the stored URL doesn't match the incoming URL, then the picture has changed.
if (!mImageURL.equals(pictureURL)) {
// Stops any ongoing downloads for this ImageView
PhotoManager.removeDownload(mDownloadThread, mImageURL);
} else {
// The stored URL matches the incoming URL. Returns without doing any work.
return;
}
}
// Sets the Drawable for this ImageView
setImageDrawable(imageDrawable);
// Stores the picture URL for this ImageView
mImageURL = pictureURL;
// If the draw operation for this ImageVIew has completed, and the picture URL isn't empty
if ((mIsDrawn) && (pictureURL != null)) {
// Sets the cache flag
mCacheFlag = cacheFlag;
/*
* Starts a download of the picture file. Notice that if caching is on, the picture
* file's contents may be taken from the cache.
*/
mDownloadThread = PhotoManager.startDownload(this, cacheFlag);
}
}
/**
* Sets the Drawable for this ImageView
* @param drawable A Drawable to use for the ImageView
*/
public void setStatusDrawable(Drawable drawable) {
// If the View is empty, sets a Drawable as its content
if (mThisView == null) {
setImageDrawable(drawable);
}
}
/**
* Sets the content of this ImageView to be a Drawable resource
* @param resId
*/
public void setStatusResource(int resId) {
// If the View is empty, provides it with a Drawable resource as its content
if (mThisView == null) {
setImageResource(resId);
}
}
}