ExtendedBitmapDrawable.java revision 9c6ac19d4a3d39b7c2992060957920118ff56a65
114110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne/* 214110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * Copyright (C) 2013 The Android Open Source Project 314110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * 414110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * Licensed under the Apache License, Version 2.0 (the "License"); 514110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * you may not use this file except in compliance with the License. 614110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * You may obtain a copy of the License at 714110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * 814110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * http://www.apache.org/licenses/LICENSE-2.0 914110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * 1014110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * Unless required by applicable law or agreed to in writing, software 1114110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * distributed under the License is distributed on an "AS IS" BASIS, 1214110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1314110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * See the License for the specific language governing permissions and 1414110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * limitations under the License. 1514110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne */ 1614110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 1714110477887e3dc168ffc6c191e72d705051f99ePeter Collingbournepackage com.android.bitmap.drawable; 1814110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 1914110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport android.animation.Animator; 2014110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport android.animation.AnimatorListenerAdapter; 2114110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport android.animation.ValueAnimator; 2214110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport android.animation.ValueAnimator.AnimatorUpdateListener; 2314110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport android.content.res.Resources; 2414110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport android.graphics.Canvas; 2514110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport android.graphics.ColorFilter; 2614110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport android.graphics.Paint; 2714110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport android.graphics.PixelFormat; 2814110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport android.graphics.Rect; 2914110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport android.graphics.drawable.Drawable; 3014110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport android.os.Handler; 3114110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport android.util.DisplayMetrics; 3214110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport android.util.Log; 3314110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport android.view.animation.LinearInterpolator; 3414110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 3514110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport com.android.bitmap.BitmapCache; 3614110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport com.android.bitmap.DecodeAggregator; 3714110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport com.android.bitmap.DecodeTask; 3814110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport com.android.bitmap.DecodeTask.DecodeOptions; 3914110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport com.android.bitmap.R; 4014110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport com.android.bitmap.RequestKey; 4114110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport com.android.bitmap.ReusableBitmap; 42c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindolaimport com.android.bitmap.util.BitmapUtils; 4314110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport com.android.bitmap.util.RectUtils; 4414110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport com.android.bitmap.util.Trace; 4514110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 4614110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport java.util.concurrent.Executor; 4714110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport java.util.concurrent.LinkedBlockingQueue; 4814110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport java.util.concurrent.ThreadPoolExecutor; 4914110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourneimport java.util.concurrent.TimeUnit; 5014110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 5114110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne/** 5214110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * This class encapsulates all functionality needed to display a single image bitmap, 5314110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * including request creation/cancelling, data unbinding and re-binding, and fancy animations 5414110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * to draw upon state changes. 5514110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * <p> 5614110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * The actual bitmap decode work is handled by {@link DecodeTask}. 5714110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * TODO: have this class extend from BasicBitmapDrawable 58c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola */ 5914110477887e3dc168ffc6c191e72d705051f99ePeter Collingbournepublic class ExtendedBitmapDrawable extends Drawable implements DecodeTask.DecodeCallback, 6014110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne Drawable.Callback, Runnable, Parallaxable, DecodeAggregator.Callback { 6114110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 6214110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private RequestKey mCurrKey; 6314110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 64c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola private ReusableBitmap mBitmap; 6514110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private final BitmapCache mCache; 6614110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private final boolean mLimitDensity; 67c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola private DecodeAggregator mDecodeAggregator; 68c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola private DecodeTask mTask; 69c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola private int mDecodeWidth; 70c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola private int mDecodeHeight; 7114110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private int mLoadState = LOAD_STATE_UNINITIALIZED; 7214110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private float mParallaxFraction = 0.5f; 7314110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private float mParallaxSpeedMultiplier; 7414110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 75c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola // each attachment gets its own placeholder and progress indicator, to be shown, hidden, 7614110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne // and animated based on Drawable#setVisible() changes, which are in turn driven by 7714110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne // #setLoadState(). 78c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola private Placeholder mPlaceholder; 79c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola private Progress mProgress; 80c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola 81c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola private static final Executor SMALL_POOL_EXECUTOR = new ThreadPoolExecutor(4, 4, 8214110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 1, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); 8314110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 8414110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private static final Executor EXECUTOR = SMALL_POOL_EXECUTOR; 85c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola 8614110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private static final int MAX_BITMAP_DENSITY = DisplayMetrics.DENSITY_HIGH; 8714110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 8814110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private static final float VERTICAL_CENTER = 1f / 3; 8914110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 9014110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private static final int LOAD_STATE_UNINITIALIZED = 0; 9114110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private static final int LOAD_STATE_NOT_YET_LOADED = 1; 9214110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private static final int LOAD_STATE_LOADING = 2; 9314110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private static final int LOAD_STATE_LOADED = 3; 9414110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private static final int LOAD_STATE_FAILED = 4; 9514110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 9614110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private final float mDensity; 9714110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private int mProgressDelayMs; 9814110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private final Paint mPaint = new Paint(); 9914110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private final Rect mSrcRect = new Rect(); 10014110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne private final Handler mHandler = new Handler(); 10114110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 102c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola public static final boolean DEBUG = false; 103c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola public static final String TAG = ExtendedBitmapDrawable.class.getSimpleName(); 10414110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 10514110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne public ExtendedBitmapDrawable(final Resources res, final BitmapCache cache, 10614110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne final boolean limitDensity, final DecodeAggregator decodeAggregator, 107c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola final Drawable placeholder, final Drawable progress) { 10814110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne mDensity = res.getDisplayMetrics().density; 10914110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne mCache = cache; 11014110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne mLimitDensity = limitDensity; 11114110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne this.mDecodeAggregator = decodeAggregator; 11214110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne mPaint.setFilterBitmap(true); 11314110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 11414110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne final int fadeOutDurationMs = res.getInteger(R.integer.bitmap_fade_animation_duration); 11514110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne final int tileColor = res.getColor(R.color.bitmap_placeholder_background_color); 11614110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne mProgressDelayMs = res.getInteger(R.integer.bitmap_progress_animation_delay); 11714110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 11814110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne int placeholderSize = res.getDimensionPixelSize(R.dimen.placeholder_size); 11914110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne mPlaceholder = new Placeholder(placeholder.getConstantState().newDrawable(res), res, 12014110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne placeholderSize, placeholderSize, fadeOutDurationMs, tileColor); 12114110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne mPlaceholder.setCallback(this); 12214110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 12314110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne int progressBarSize = res.getDimensionPixelSize(R.dimen.progress_bar_size); 12414110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne mProgress = new Progress(progress.getConstantState().newDrawable(res), res, 12514110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne progressBarSize, progressBarSize, fadeOutDurationMs, tileColor); 12614110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne mProgress.setCallback(this); 127c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola } 12814110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne 12914110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne public RequestKey getKey() { 13014110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne return mCurrKey; 13114110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne } 132c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola 13314110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne /** 134c4850c2aa4c281a352e228aafc51fb1e30dcad02Rafael Espindola * Set the dimensions to which to decode into. For a parallax effect, ensure the height is 13514110477887e3dc168ffc6c191e72d705051f99ePeter Collingbourne * larger than the destination of the bitmap. 136 * TODO: test parallax 137 */ 138 public void setDecodeDimensions(int w, int h) { 139 mDecodeWidth = w; 140 mDecodeHeight = h; 141 decode(); 142 } 143 144 public void setParallaxSpeedMultiplier(final float parallaxSpeedMultiplier) { 145 mParallaxSpeedMultiplier = parallaxSpeedMultiplier; 146 } 147 148 public void showStaticPlaceholder() { 149 setLoadState(LOAD_STATE_FAILED); 150 } 151 152 public void unbind() { 153 setImage(null); 154 } 155 156 public void bind(RequestKey key) { 157 setImage(key); 158 } 159 160 private void setImage(final RequestKey key) { 161 if (mCurrKey != null && mCurrKey.equals(key)) { 162 return; 163 } 164 165 Trace.beginSection("set image"); 166 Trace.beginSection("release reference"); 167 if (mBitmap != null) { 168 mBitmap.releaseReference(); 169 mBitmap = null; 170 } 171 Trace.endSection(); 172 if (mCurrKey != null && mDecodeAggregator != null) { 173 mDecodeAggregator.forget(mCurrKey); 174 } 175 mCurrKey = key; 176 177 if (mTask != null) { 178 mTask.cancel(); 179 mTask = null; 180 } 181 182 mHandler.removeCallbacks(this); 183 // start from a clean slate on every bind 184 // this allows the initial transition to be specially instantaneous, so e.g. a cache hit 185 // doesn't unnecessarily trigger a fade-in 186 setLoadState(LOAD_STATE_UNINITIALIZED); 187 188 if (key == null) { 189 invalidateSelf(); 190 Trace.endSection(); 191 return; 192 } 193 194 // find cached entry here and skip decode if found. 195 final ReusableBitmap cached = mCache.get(key, true /* incrementRefCount */); 196 if (cached != null) { 197 setBitmap(cached); 198 if (DEBUG) { 199 Log.d(TAG, String.format("CACHE HIT key=%s", mCurrKey)); 200 } 201 } else { 202 decode(); 203 if (DEBUG) { 204 Log.d(TAG, String.format( 205 "CACHE MISS key=%s\ncache=%s", mCurrKey, mCache.toDebugString())); 206 } 207 } 208 Trace.endSection(); 209 } 210 211 @Override 212 public void setParallaxFraction(float fraction) { 213 mParallaxFraction = fraction; 214 } 215 216 @Override 217 public void draw(final Canvas canvas) { 218 final Rect bounds = getBounds(); 219 if (bounds.isEmpty()) { 220 return; 221 } 222 223 if (mBitmap != null && mBitmap.bmp != null) { 224 BitmapUtils.calculateCroppedSrcRect( 225 mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight(), 226 bounds.width(), bounds.height(), 227 bounds.height(), Integer.MAX_VALUE, 228 mParallaxFraction, false /* absoluteFraction */, 229 mParallaxSpeedMultiplier, mSrcRect); 230 231 final int orientation = mBitmap.getOrientation(); 232 // calculateCroppedSrcRect() gave us the source rectangle "as if" the orientation has 233 // been corrected. We need to decode the uncorrected source rectangle. Calculate true 234 // coordinates. 235 RectUtils.rotateRectForOrientation(orientation, 236 new Rect(0, 0, mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight()), 237 mSrcRect); 238 239 // We may need to rotate the canvas, so we also have to rotate the bounds. 240 final Rect rotatedBounds = new Rect(bounds); 241 RectUtils.rotateRect(orientation, bounds.centerX(), bounds.centerY(), rotatedBounds); 242 243 // Rotate the canvas. 244 canvas.save(); 245 canvas.rotate(orientation, bounds.centerX(), bounds.centerY()); 246 canvas.drawBitmap(mBitmap.bmp, mSrcRect, rotatedBounds, mPaint); 247 canvas.restore(); 248 } 249 250 // Draw the two possible overlay layers in reverse-priority order. 251 // (each layer will no-op the draw when appropriate) 252 // This ordering means cross-fade transitions are just fade-outs of each layer. 253 mProgress.draw(canvas); 254 mPlaceholder.draw(canvas); 255 } 256 257 @Override 258 public void setAlpha(int alpha) { 259 final int old = mPaint.getAlpha(); 260 mPaint.setAlpha(alpha); 261 mPlaceholder.setAlpha(alpha); 262 mProgress.setAlpha(alpha); 263 if (alpha != old) { 264 invalidateSelf(); 265 } 266 } 267 268 @Override 269 public void setColorFilter(ColorFilter cf) { 270 mPaint.setColorFilter(cf); 271 mPlaceholder.setColorFilter(cf); 272 mProgress.setColorFilter(cf); 273 invalidateSelf(); 274 } 275 276 @Override 277 public int getOpacity() { 278 return (mBitmap != null && (mBitmap.bmp.hasAlpha() || mPaint.getAlpha() < 255)) ? 279 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 280 } 281 282 @Override 283 protected void onBoundsChange(Rect bounds) { 284 super.onBoundsChange(bounds); 285 286 mPlaceholder.setBounds(bounds); 287 mProgress.setBounds(bounds); 288 } 289 290 @Override 291 public void onDecodeBegin(final RequestKey key) { 292 if (mDecodeAggregator != null) { 293 mDecodeAggregator.expect(key, this); 294 } else { 295 onBecomeFirstExpected(key); 296 } 297 } 298 299 @Override 300 public void onBecomeFirstExpected(final RequestKey key) { 301 if (!key.equals(mCurrKey)) { 302 return; 303 } 304 // normally, we'd transition to the LOADING state now, but we want to delay that a bit 305 // to minimize excess occurrences of the rotating spinner 306 mHandler.postDelayed(this, mProgressDelayMs); 307 } 308 309 @Override 310 public void run() { 311 if (mLoadState == LOAD_STATE_NOT_YET_LOADED) { 312 setLoadState(LOAD_STATE_LOADING); 313 } 314 } 315 316 @Override 317 public void onDecodeComplete(final RequestKey key, final ReusableBitmap result) { 318 if (mDecodeAggregator != null) { 319 mDecodeAggregator.execute(key, new Runnable() { 320 @Override 321 public void run() { 322 onDecodeCompleteImpl(key, result); 323 } 324 325 @Override 326 public String toString() { 327 return "DONE"; 328 } 329 }); 330 } else { 331 onDecodeCompleteImpl(key, result); 332 } 333 } 334 335 private void onDecodeCompleteImpl(final RequestKey key, final ReusableBitmap result) { 336 if (key.equals(mCurrKey)) { 337 setBitmap(result); 338 } else { 339 // if the requests don't match (i.e. this request is stale), decrement the 340 // ref count to allow the bitmap to be pooled 341 if (result != null) { 342 result.releaseReference(); 343 } 344 } 345 } 346 347 @Override 348 public void onDecodeCancel(final RequestKey key) { 349 if (mDecodeAggregator != null) { 350 mDecodeAggregator.forget(key); 351 } 352 } 353 354 private void setBitmap(ReusableBitmap bmp) { 355 if (mBitmap != null && mBitmap != bmp) { 356 mBitmap.releaseReference(); 357 } 358 mBitmap = bmp; 359 setLoadState((bmp != null) ? LOAD_STATE_LOADED : LOAD_STATE_FAILED); 360 invalidateSelf(); 361 } 362 363 private void decode() { 364 final int bufferW; 365 final int bufferH; 366 367 if (mCurrKey == null) { 368 return; 369 } 370 371 Trace.beginSection("decode"); 372 if (mLimitDensity) { 373 final float scale = 374 Math.min(1f, (float) MAX_BITMAP_DENSITY / DisplayMetrics.DENSITY_DEFAULT 375 / mDensity); 376 bufferW = (int) (mDecodeWidth * scale); 377 bufferH = (int) (mDecodeHeight * scale); 378 } else { 379 bufferW = mDecodeWidth; 380 bufferH = mDecodeHeight; 381 } 382 383 if (bufferW == 0 || bufferH == 0) { 384 Trace.endSection(); 385 return; 386 } 387 if (mTask != null) { 388 mTask.cancel(); 389 } 390 setLoadState(LOAD_STATE_NOT_YET_LOADED); 391 final DecodeOptions opts = new DecodeOptions(bufferW, bufferH, VERTICAL_CENTER, 392 DecodeOptions.STRATEGY_ROUND_NEAREST); 393 // TODO: file is null because we expect this class to extend BasicBitmapDrawable soon. 394 mTask = new DecodeTask(mCurrKey, opts, null /* file */, this, mCache); 395 mTask.executeOnExecutor(EXECUTOR); 396 Trace.endSection(); 397 } 398 399 private void setLoadState(int loadState) { 400 if (DEBUG) { 401 Log.v(TAG, String.format("IN setLoadState. old=%s new=%s key=%s this=%s", 402 mLoadState, loadState, mCurrKey, this)); 403 } 404 if (mLoadState == loadState) { 405 if (DEBUG) { 406 Log.v(TAG, "OUT no-op setLoadState"); 407 } 408 return; 409 } 410 411 Trace.beginSection("set load state"); 412 switch (loadState) { 413 // This state differs from LOADED in that the subsequent state transition away from 414 // UNINITIALIZED will not have a fancy transition. This allows list item binds to 415 // cached data to take immediate effect without unnecessary whizzery. 416 case LOAD_STATE_UNINITIALIZED: 417 mPlaceholder.reset(); 418 mProgress.reset(); 419 break; 420 case LOAD_STATE_NOT_YET_LOADED: 421 mPlaceholder.setPulseEnabled(true); 422 mPlaceholder.setVisible(true); 423 mProgress.setVisible(false); 424 break; 425 case LOAD_STATE_LOADING: 426 mPlaceholder.setVisible(false); 427 mProgress.setVisible(true); 428 break; 429 case LOAD_STATE_LOADED: 430 mPlaceholder.setVisible(false); 431 mProgress.setVisible(false); 432 break; 433 case LOAD_STATE_FAILED: 434 mPlaceholder.setPulseEnabled(false); 435 mPlaceholder.setVisible(true); 436 mProgress.setVisible(false); 437 break; 438 } 439 Trace.endSection(); 440 441 mLoadState = loadState; 442 boolean placeholderVisible = mPlaceholder != null && mPlaceholder.isVisible(); 443 boolean progressVisible = mProgress != null && mProgress.isVisible(); 444 445 if (DEBUG) { 446 Log.v(TAG, String.format("OUT stateful setLoadState. new=%s placeholder=%s progress=%s", 447 loadState, placeholderVisible, progressVisible)); 448 } 449 } 450 451 @Override 452 public void invalidateDrawable(Drawable who) { 453 invalidateSelf(); 454 } 455 456 @Override 457 public void scheduleDrawable(Drawable who, Runnable what, long when) { 458 scheduleSelf(what, when); 459 } 460 461 @Override 462 public void unscheduleDrawable(Drawable who, Runnable what) { 463 unscheduleSelf(what); 464 } 465 466 private static class Placeholder extends TileDrawable { 467 468 private final ValueAnimator mPulseAnimator; 469 private boolean mPulseEnabled = true; 470 private float mPulseAlphaFraction = 1f; 471 472 public Placeholder(Drawable placeholder, Resources res, 473 int placeholderWidth, int placeholderHeight, int fadeOutDurationMs, 474 int tileColor) { 475 super(placeholder, placeholderWidth, placeholderHeight, tileColor, fadeOutDurationMs); 476 mPulseAnimator = ValueAnimator.ofInt(55, 255) 477 .setDuration(res.getInteger(R.integer.bitmap_placeholder_animation_duration)); 478 mPulseAnimator.setRepeatCount(ValueAnimator.INFINITE); 479 mPulseAnimator.setRepeatMode(ValueAnimator.REVERSE); 480 mPulseAnimator.addUpdateListener(new AnimatorUpdateListener() { 481 @Override 482 public void onAnimationUpdate(ValueAnimator animation) { 483 mPulseAlphaFraction = ((Integer) animation.getAnimatedValue()) / 255f; 484 setInnerAlpha(getCurrentAlpha()); 485 } 486 }); 487 mFadeOutAnimator.addListener(new AnimatorListenerAdapter() { 488 @Override 489 public void onAnimationEnd(Animator animation) { 490 stopPulsing(); 491 } 492 }); 493 } 494 495 @Override 496 public void setInnerAlpha(final int alpha) { 497 super.setInnerAlpha((int) (alpha * mPulseAlphaFraction)); 498 } 499 500 public void setPulseEnabled(boolean enabled) { 501 mPulseEnabled = enabled; 502 if (!mPulseEnabled) { 503 stopPulsing(); 504 } 505 } 506 507 private void stopPulsing() { 508 if (mPulseAnimator != null) { 509 mPulseAnimator.cancel(); 510 mPulseAlphaFraction = 1f; 511 setInnerAlpha(getCurrentAlpha()); 512 } 513 } 514 515 @Override 516 public boolean setVisible(boolean visible) { 517 final boolean changed = super.setVisible(visible); 518 if (changed) { 519 if (isVisible()) { 520 // start 521 if (mPulseAnimator != null && mPulseEnabled) { 522 mPulseAnimator.start(); 523 } 524 } else { 525 // can't cancel the pulsing yet-- wait for the fade-out animation to end 526 // one exception: if alpha is already zero, there is no fade-out, so stop now 527 if (getCurrentAlpha() == 0) { 528 stopPulsing(); 529 } 530 } 531 } 532 return changed; 533 } 534 535 } 536 537 private static class Progress extends TileDrawable { 538 539 private final ValueAnimator mRotateAnimator; 540 541 public Progress(Drawable progress, Resources res, 542 int progressBarWidth, int progressBarHeight, int fadeOutDurationMs, 543 int tileColor) { 544 super(progress, progressBarWidth, progressBarHeight, tileColor, fadeOutDurationMs); 545 546 mRotateAnimator = ValueAnimator.ofInt(0, 10000) 547 .setDuration(res.getInteger(R.integer.bitmap_progress_animation_duration)); 548 mRotateAnimator.setInterpolator(new LinearInterpolator()); 549 mRotateAnimator.setRepeatCount(ValueAnimator.INFINITE); 550 mRotateAnimator.addUpdateListener(new AnimatorUpdateListener() { 551 @Override 552 public void onAnimationUpdate(ValueAnimator animation) { 553 setLevel((Integer) animation.getAnimatedValue()); 554 } 555 }); 556 mFadeOutAnimator.addListener(new AnimatorListenerAdapter() { 557 @Override 558 public void onAnimationEnd(Animator animation) { 559 if (mRotateAnimator != null) { 560 mRotateAnimator.cancel(); 561 } 562 } 563 }); 564 } 565 566 @Override 567 public boolean setVisible(boolean visible) { 568 final boolean changed = super.setVisible(visible); 569 if (changed) { 570 if (isVisible()) { 571 if (mRotateAnimator != null) { 572 mRotateAnimator.start(); 573 } 574 } else { 575 // can't cancel the rotate yet-- wait for the fade-out animation to end 576 // one exception: if alpha is already zero, there is no fade-out, so stop now 577 if (getCurrentAlpha() == 0 && mRotateAnimator != null) { 578 mRotateAnimator.cancel(); 579 } 580 } 581 } 582 return changed; 583 } 584 585 } 586} 587