1 /* 2 * Copyright (C) 2012 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 17 package com.example.android.threadsample; 18 19 import android.content.Context; 20 import android.content.res.TypedArray; 21 import android.graphics.Bitmap; 22 import android.graphics.Canvas; 23 import android.graphics.drawable.Drawable; 24 import android.net.Uri; 25 import android.util.AttributeSet; 26 import android.view.View; 27 import android.widget.ImageView; 28 29 30 import java.lang.ref.WeakReference; 31 import java.net.URL; 32 33 /** 34 * This class extends the standard Android ImageView View class with some features 35 * that are useful for downloading, decoding, and displaying Picasa images. 36 * 37 */ 38 public class PhotoView extends ImageView { 39 40 // Indicates if caching should be used 41 private boolean mCacheFlag; 42 43 // Status flag that indicates if onDraw has completed 44 private boolean mIsDrawn; 45 46 /* 47 * Creates a weak reference to the ImageView in this object. The weak 48 * reference prevents memory leaks and crashes, because it automatically tracks the "state" of 49 * the variable it backs. If the reference becomes invalid, the weak reference is garbage- 50 * collected. 51 * This technique is important for referring to objects that are part of a component lifecycle. 52 * Using a hard reference may cause memory leaks as the value continues to change; even worse, 53 * it can cause crashes if the underlying component is destroyed. Using a weak reference to 54 * a View ensures that the reference is more transitory in nature. 55 */ 56 private WeakReference<View> mThisView; 57 58 // Contains the ID of the internal View 59 private int mHideShowResId = -1; 60 61 // The URL that points to the source of the image for this ImageView 62 private URL mImageURL; 63 64 // The Thread that will be used to download the image for this ImageView 65 private PhotoTask mDownloadThread; 66 67 /** 68 * Creates an ImageDownloadView with no settings 69 * @param context A context for the View 70 */ PhotoView(Context context)71 public PhotoView(Context context) { 72 super(context); 73 } 74 75 /** 76 * Creates an ImageDownloadView and gets attribute values 77 * @param context A Context to use with the View 78 * @param attributeSet The entire set of attributes for the View 79 */ PhotoView(Context context, AttributeSet attributeSet)80 public PhotoView(Context context, AttributeSet attributeSet) { 81 super(context, attributeSet); 82 83 // Gets attributes associated with the attribute set 84 getAttributes(attributeSet); 85 } 86 87 /** 88 * Creates an ImageDownloadView, gets attribute values, and applies a default style 89 * @param context A context for the View 90 * @param attributeSet The entire set of attributes for the View 91 * @param defaultStyle The default style to use with the View 92 */ PhotoView(Context context, AttributeSet attributeSet, int defaultStyle)93 public PhotoView(Context context, AttributeSet attributeSet, int defaultStyle) { 94 super(context, attributeSet, defaultStyle); 95 96 // Gets attributes associated with the attribute set 97 getAttributes(attributeSet); 98 } 99 100 /** 101 * Gets the resource ID for the hideShowSibling resource 102 * @param attributeSet The entire set of attributes for the View 103 */ getAttributes(AttributeSet attributeSet)104 private void getAttributes(AttributeSet attributeSet) { 105 106 // Gets an array of attributes for the View 107 TypedArray attributes = 108 getContext().obtainStyledAttributes(attributeSet, R.styleable.ImageDownloaderView); 109 110 // Gets the resource Id of the View to hide or show 111 mHideShowResId = 112 attributes.getResourceId(R.styleable.ImageDownloaderView_hideShowSibling, -1); 113 114 // Returns the array for re-use 115 attributes.recycle(); 116 } 117 118 /** 119 * Sets the visibility of the PhotoView 120 * @param visState The visibility state (see View.setVisibility) 121 */ showView(int visState)122 private void showView(int visState) { 123 // If the View contains something 124 if (mThisView != null) { 125 126 // Gets a local hard reference to the View 127 View localView = mThisView.get(); 128 129 // If the weak reference actually contains something, set the visibility 130 if (localView != null) 131 localView.setVisibility(visState); 132 } 133 } 134 135 /** 136 * Sets the image in this ImageView to null, and makes the View visible 137 */ clearImage()138 public void clearImage() { 139 setImageDrawable(null); 140 showView(View.VISIBLE); 141 } 142 143 /** 144 * Returns the URL of the picture associated with this ImageView 145 * @return a URL 146 */ getLocation()147 final URL getLocation() { 148 return mImageURL; 149 } 150 151 /* 152 * This callback is invoked when the system attaches the ImageView to a Window. The callback 153 * is invoked before onDraw(), but may be invoked after onMeasure() 154 */ 155 @Override onAttachedToWindow()156 protected void onAttachedToWindow() { 157 // Always call the supermethod first 158 super.onAttachedToWindow(); 159 160 // If the sibling View is set and the parent of the ImageView is itself a View 161 if ((this.mHideShowResId != -1) && ((getParent() instanceof View))) { 162 163 // Gets a handle to the sibling View 164 View localView = ((View) getParent()).findViewById(this.mHideShowResId); 165 166 // If the sibling View contains something, make it the weak reference for this View 167 if (localView != null) { 168 this.mThisView = new WeakReference<View>(localView); 169 } 170 } 171 } 172 173 /* 174 * This callback is invoked when the ImageView is removed from a Window. It "unsets" variables 175 * to prevent memory leaks. 176 */ 177 @Override onDetachedFromWindow()178 protected void onDetachedFromWindow() { 179 180 // Clears out the image drawable, turns off the cache, disconnects the view from a URL 181 setImageURL(null, false, null); 182 183 // Gets the current Drawable, or null if no Drawable is attached 184 Drawable localDrawable = getDrawable(); 185 186 // if the Drawable is null, unbind it from this VIew 187 if (localDrawable != null) 188 localDrawable.setCallback(null); 189 190 // If this View still exists, clears the weak reference, then sets the reference to null 191 if (mThisView != null) { 192 mThisView.clear(); 193 mThisView = null; 194 } 195 196 // Sets the downloader thread to null 197 this.mDownloadThread = null; 198 199 // Always call the super method last 200 super.onDetachedFromWindow(); 201 } 202 203 /* 204 * This callback is invoked when the system tells the View to draw itself. If the View isn't 205 * already drawn, and its URL isn't null, it invokes a Thread to download the image. Otherwise, 206 * it simply passes the existing Canvas to the super method 207 */ 208 @Override onDraw(Canvas canvas)209 protected void onDraw(Canvas canvas) { 210 // If the image isn't already drawn, and the URL is set 211 if ((!mIsDrawn) && (mImageURL != null)) { 212 213 // Starts downloading this View, using the current cache setting 214 mDownloadThread = PhotoManager.startDownload(this, mCacheFlag); 215 216 // After successfully downloading the image, this marks that it's available. 217 mIsDrawn = true; 218 } 219 // Always call the super method last 220 super.onDraw(canvas); 221 } 222 223 /** 224 * Sets the current View weak reference to be the incoming View. See the definition of 225 * mThisView 226 * @param view the View to use as the new WeakReference 227 */ setHideView(View view)228 public void setHideView(View view) { 229 this.mThisView = new WeakReference<View>(view); 230 } 231 232 @Override setImageBitmap(Bitmap paramBitmap)233 public void setImageBitmap(Bitmap paramBitmap) { 234 super.setImageBitmap(paramBitmap); 235 } 236 237 @Override setImageDrawable(Drawable drawable)238 public void setImageDrawable(Drawable drawable) { 239 // The visibility of the View 240 int viewState; 241 242 /* 243 * Sets the View state to visible if the method is called with a null argument (the 244 * image is being cleared). Otherwise, sets the View state to invisible before refreshing 245 * it. 246 */ 247 if (drawable == null) { 248 249 viewState = View.VISIBLE; 250 } else { 251 252 viewState = View.INVISIBLE; 253 } 254 // Either hides or shows the View, depending on the view state 255 showView(viewState); 256 257 // Invokes the supermethod with the provided drawable 258 super.setImageDrawable(drawable); 259 } 260 261 /* 262 * Displays a drawable in the View 263 */ 264 @Override setImageResource(int resId)265 public void setImageResource(int resId) { 266 super.setImageResource(resId); 267 } 268 269 /* 270 * Sets the URI for the Image 271 */ 272 @Override setImageURI(Uri uri)273 public void setImageURI(Uri uri) { 274 super.setImageURI(uri); 275 } 276 277 /** 278 * Attempts to set the picture URL for this ImageView and then download the picture. 279 * <p> 280 * If the picture URL for this view is already set, and the input URL is not the same as the 281 * stored URL, then the picture has moved and any existing downloads are stopped. 282 * <p> 283 * If the input URL is the same as the stored URL, then nothing needs to be done. 284 * <p> 285 * If the stored URL is null, then this method starts a download and decode of the picture 286 * @param pictureURL An incoming URL for a Picasa picture 287 * @param cacheFlag Whether to use caching when doing downloading and decoding 288 * @param imageDrawable The Drawable to use for this ImageView 289 */ setImageURL(URL pictureURL, boolean cacheFlag, Drawable imageDrawable)290 public void setImageURL(URL pictureURL, boolean cacheFlag, Drawable imageDrawable) { 291 // If the picture URL for this ImageView is already set 292 if (mImageURL != null) { 293 294 // If the stored URL doesn't match the incoming URL, then the picture has changed. 295 if (!mImageURL.equals(pictureURL)) { 296 297 // Stops any ongoing downloads for this ImageView 298 PhotoManager.removeDownload(mDownloadThread, mImageURL); 299 } else { 300 301 // The stored URL matches the incoming URL. Returns without doing any work. 302 return; 303 } 304 } 305 306 // Sets the Drawable for this ImageView 307 setImageDrawable(imageDrawable); 308 309 // Stores the picture URL for this ImageView 310 mImageURL = pictureURL; 311 312 // If the draw operation for this ImageVIew has completed, and the picture URL isn't empty 313 if ((mIsDrawn) && (pictureURL != null)) { 314 315 // Sets the cache flag 316 mCacheFlag = cacheFlag; 317 318 /* 319 * Starts a download of the picture file. Notice that if caching is on, the picture 320 * file's contents may be taken from the cache. 321 */ 322 mDownloadThread = PhotoManager.startDownload(this, cacheFlag); 323 } 324 } 325 326 /** 327 * Sets the Drawable for this ImageView 328 * @param drawable A Drawable to use for the ImageView 329 */ setStatusDrawable(Drawable drawable)330 public void setStatusDrawable(Drawable drawable) { 331 332 // If the View is empty, sets a Drawable as its content 333 if (mThisView == null) { 334 setImageDrawable(drawable); 335 } 336 } 337 338 /** 339 * Sets the content of this ImageView to be a Drawable resource 340 * @param resId 341 */ setStatusResource(int resId)342 public void setStatusResource(int resId) { 343 344 // If the View is empty, provides it with a Drawable resource as its content 345 if (mThisView == null) { 346 setImageResource(resId); 347 } 348 } 349 } 350