ExtendedBitmapDrawable.java revision 2e4d0863dba53435372ec96538f2ef3e1c3675bf
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 17package com.android.bitmap.drawable; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ValueAnimator; 22import android.animation.ValueAnimator.AnimatorUpdateListener; 23import android.content.res.Resources; 24import android.graphics.Canvas; 25import android.graphics.ColorFilter; 26import android.graphics.Rect; 27import android.graphics.drawable.Drawable; 28import android.os.Handler; 29import android.util.Log; 30import android.view.animation.LinearInterpolator; 31 32import com.android.bitmap.BitmapCache; 33import com.android.bitmap.DecodeAggregator; 34import com.android.bitmap.DecodeTask; 35import com.android.bitmap.R; 36import com.android.bitmap.RequestKey; 37import com.android.bitmap.RequestKey.FileDescriptorFactory; 38import com.android.bitmap.ReusableBitmap; 39import com.android.bitmap.util.Trace; 40 41/** 42 * This class encapsulates all functionality needed to display a single image bitmap, 43 * including request creation/cancelling, data unbinding and re-binding, and fancy animations 44 * to draw upon state changes. 45 * <p> 46 * The actual bitmap decode work is handled by {@link DecodeTask}. 47 * TODO: have this class extend from BasicBitmapDrawable 48 */ 49public class ExtendedBitmapDrawable extends BasicBitmapDrawable implements 50 Runnable, Parallaxable, DecodeAggregator.Callback { 51 52 // Ordered display. 53 private DecodeAggregator mDecodeAggregator; 54 55 // Parallax. 56 private float mParallaxFraction = 0.5f; 57 private float mParallaxSpeedMultiplier; 58 private static final float DECODE_VERTICAL_CENTER = 1f / 3; 59 60 // Placeholder and progress. 61 private static final int LOAD_STATE_UNINITIALIZED = 0; 62 private static final int LOAD_STATE_NOT_YET_LOADED = 1; 63 private static final int LOAD_STATE_LOADING = 2; 64 private static final int LOAD_STATE_LOADED = 3; 65 private static final int LOAD_STATE_FAILED = 4; 66 private int mLoadState = LOAD_STATE_UNINITIALIZED; 67 private Placeholder mPlaceholder; 68 private Progress mProgress; 69 private int mProgressDelayMs; 70 private final Handler mHandler = new Handler(); 71 72 public static final boolean DEBUG = false; 73 public static final String TAG = ExtendedBitmapDrawable.class.getSimpleName(); 74 75 public ExtendedBitmapDrawable(final Resources res, final BitmapCache cache, 76 final boolean limitDensity, final DecodeAggregator decodeAggregator, 77 final Drawable placeholder, final Drawable progress) { 78 super(res, cache, limitDensity); 79 80 // Ordered display. 81 this.mDecodeAggregator = decodeAggregator; 82 83 // Placeholder and progress. 84 final int fadeOutDurationMs = res.getInteger(R.integer.bitmap_fade_animation_duration); 85 final int tileColor = res.getColor(R.color.bitmap_placeholder_background_color); 86 mProgressDelayMs = res.getInteger(R.integer.bitmap_progress_animation_delay); 87 88 int placeholderSize = res.getDimensionPixelSize(R.dimen.placeholder_size); 89 mPlaceholder = new Placeholder(placeholder.getConstantState().newDrawable(res), res, 90 placeholderSize, placeholderSize, fadeOutDurationMs, tileColor); 91 mPlaceholder.setCallback(this); 92 93 int progressBarSize = res.getDimensionPixelSize(R.dimen.progress_bar_size); 94 mProgress = new Progress(progress.getConstantState().newDrawable(res), res, 95 progressBarSize, progressBarSize, fadeOutDurationMs, tileColor); 96 mProgress.setCallback(this); 97 } 98 99 @Override 100 public void setParallaxFraction(float fraction) { 101 mParallaxFraction = fraction; 102 invalidateSelf(); 103 } 104 105 public void setParallaxSpeedMultiplier(final float parallaxSpeedMultiplier) { 106 mParallaxSpeedMultiplier = parallaxSpeedMultiplier; 107 invalidateSelf(); 108 } 109 110 /** 111 * This sets the drawable to the failed state, which remove all animations from the placeholder. 112 * This is different from unbinding to the uninitialized state, where we expect animations. 113 */ 114 public void showStaticPlaceholder() { 115 setLoadState(LOAD_STATE_FAILED); 116 } 117 118 @Override 119 protected void setImage(final RequestKey key) { 120 if (mCurrKey != null && mCurrKey.equals(key)) { 121 return; 122 } 123 124 if (mCurrKey != null && mDecodeAggregator != null) { 125 mDecodeAggregator.forget(mCurrKey); 126 } 127 128 mHandler.removeCallbacks(this); 129 // start from a clean slate on every bind 130 // this allows the initial transition to be specially instantaneous, so e.g. a cache hit 131 // doesn't unnecessarily trigger a fade-in 132 setLoadState(LOAD_STATE_UNINITIALIZED); 133 if (key == null) { 134 setLoadState(LOAD_STATE_FAILED); 135 } 136 137 super.setImage(key); 138 } 139 140 @Override 141 protected void setBitmap(ReusableBitmap bmp) { 142 setLoadState((bmp != null) ? LOAD_STATE_LOADED : LOAD_STATE_FAILED); 143 144 super.setBitmap(bmp); 145 } 146 147 @Override 148 protected void decode(final FileDescriptorFactory factory) { 149 boolean executeStateChange = shouldExecuteStateChange(); 150 if (executeStateChange) { 151 setLoadState(LOAD_STATE_NOT_YET_LOADED); 152 } 153 154 super.decode(factory); 155 } 156 157 protected boolean shouldExecuteStateChange() { 158 // TODO: AttachmentDrawable should override this method to match prev and curr request keys. 159 return /* opts.stateChanges */ true; 160 } 161 162 @Override 163 public float getDrawVerticalCenter() { 164 return mParallaxFraction; 165 } 166 167 @Override 168 protected float getDrawVerticalOffsetMultiplier() { 169 return mParallaxSpeedMultiplier; 170 } 171 172 @Override 173 protected float getDecodeVerticalCenter() { 174 return DECODE_VERTICAL_CENTER; 175 } 176 177 @Override 178 public void draw(final Canvas canvas) { 179 final Rect bounds = getBounds(); 180 if (bounds.isEmpty()) { 181 return; 182 } 183 184 super.draw(canvas); 185 186 // Draw the two possible overlay layers in reverse-priority order. 187 // (each layer will no-op the draw when appropriate) 188 // This ordering means cross-fade transitions are just fade-outs of each layer. 189 mProgress.draw(canvas); 190 mPlaceholder.draw(canvas); 191 } 192 193 @Override 194 public void setAlpha(int alpha) { 195 final int old = mPaint.getAlpha(); 196 super.setAlpha(alpha); 197 mPlaceholder.setAlpha(alpha); 198 mProgress.setAlpha(alpha); 199 if (alpha != old) { 200 invalidateSelf(); 201 } 202 } 203 204 @Override 205 public void setColorFilter(ColorFilter cf) { 206 super.setColorFilter(cf); 207 mPlaceholder.setColorFilter(cf); 208 mProgress.setColorFilter(cf); 209 invalidateSelf(); 210 } 211 212 @Override 213 protected void onBoundsChange(Rect bounds) { 214 super.onBoundsChange(bounds); 215 216 mPlaceholder.setBounds(bounds); 217 mProgress.setBounds(bounds); 218 } 219 220 @Override 221 public void onDecodeBegin(final RequestKey key) { 222 if (mDecodeAggregator != null) { 223 mDecodeAggregator.expect(key, this); 224 } else { 225 onBecomeFirstExpected(key); 226 } 227 super.onDecodeBegin(key); 228 } 229 230 @Override 231 public void onBecomeFirstExpected(final RequestKey key) { 232 if (!key.equals(mCurrKey)) { 233 return; 234 } 235 // normally, we'd transition to the LOADING state now, but we want to delay that a bit 236 // to minimize excess occurrences of the rotating spinner 237 mHandler.postDelayed(this, mProgressDelayMs); 238 } 239 240 @Override 241 public void run() { 242 if (mLoadState == LOAD_STATE_NOT_YET_LOADED) { 243 setLoadState(LOAD_STATE_LOADING); 244 } 245 } 246 247 @Override 248 public void onDecodeComplete(final RequestKey key, final ReusableBitmap result) { 249 if (mDecodeAggregator != null) { 250 mDecodeAggregator.execute(key, new Runnable() { 251 @Override 252 public void run() { 253 ExtendedBitmapDrawable.super.onDecodeComplete(key, result); 254 } 255 256 @Override 257 public String toString() { 258 return "DONE"; 259 } 260 }); 261 } else { 262 super.onDecodeComplete(key, result); 263 } 264 } 265 266 @Override 267 public void onDecodeCancel(final RequestKey key) { 268 if (mDecodeAggregator != null) { 269 mDecodeAggregator.forget(key); 270 } 271 super.onDecodeCancel(key); 272 } 273 274 /** 275 * Each attachment gets its own placeholder and progress indicator, to be shown, hidden, 276 * and animated based on Drawable#setVisible() changes, which are in turn driven by 277 * setLoadState(). 278 */ 279 private void setLoadState(int loadState) { 280 if (DEBUG) { 281 Log.v(TAG, String.format("IN setLoadState. old=%s new=%s key=%s this=%s", 282 mLoadState, loadState, mCurrKey, this)); 283 } 284 if (mLoadState == loadState) { 285 if (DEBUG) { 286 Log.v(TAG, "OUT no-op setLoadState"); 287 } 288 return; 289 } 290 291 Trace.beginSection("set load state"); 292 switch (loadState) { 293 // This state differs from LOADED in that the subsequent state transition away from 294 // UNINITIALIZED will not have a fancy transition. This allows list item binds to 295 // cached data to take immediate effect without unnecessary whizzery. 296 case LOAD_STATE_UNINITIALIZED: 297 mPlaceholder.reset(); 298 mProgress.reset(); 299 break; 300 case LOAD_STATE_NOT_YET_LOADED: 301 mPlaceholder.setPulseEnabled(true); 302 mPlaceholder.setVisible(true); 303 mProgress.setVisible(false); 304 break; 305 case LOAD_STATE_LOADING: 306 mPlaceholder.setVisible(false); 307 mProgress.setVisible(true); 308 break; 309 case LOAD_STATE_LOADED: 310 mPlaceholder.setVisible(false); 311 mProgress.setVisible(false); 312 break; 313 case LOAD_STATE_FAILED: 314 mPlaceholder.setPulseEnabled(false); 315 mPlaceholder.setVisible(true); 316 mProgress.setVisible(false); 317 break; 318 } 319 Trace.endSection(); 320 321 mLoadState = loadState; 322 boolean placeholderVisible = mPlaceholder != null && mPlaceholder.isVisible(); 323 boolean progressVisible = mProgress != null && mProgress.isVisible(); 324 325 if (DEBUG) { 326 Log.v(TAG, String.format("OUT stateful setLoadState. new=%s placeholder=%s progress=%s", 327 loadState, placeholderVisible, progressVisible)); 328 } 329 } 330 331 private static class Placeholder extends TileDrawable { 332 333 private final ValueAnimator mPulseAnimator; 334 private boolean mPulseEnabled = true; 335 private float mPulseAlphaFraction = 1f; 336 337 public Placeholder(Drawable placeholder, Resources res, 338 int placeholderWidth, int placeholderHeight, int fadeOutDurationMs, 339 int tileColor) { 340 super(placeholder, placeholderWidth, placeholderHeight, tileColor, fadeOutDurationMs); 341 mPulseAnimator = ValueAnimator.ofInt(55, 255) 342 .setDuration(res.getInteger(R.integer.bitmap_placeholder_animation_duration)); 343 mPulseAnimator.setRepeatCount(ValueAnimator.INFINITE); 344 mPulseAnimator.setRepeatMode(ValueAnimator.REVERSE); 345 mPulseAnimator.addUpdateListener(new AnimatorUpdateListener() { 346 @Override 347 public void onAnimationUpdate(ValueAnimator animation) { 348 mPulseAlphaFraction = ((Integer) animation.getAnimatedValue()) / 255f; 349 setInnerAlpha(getCurrentAlpha()); 350 } 351 }); 352 mFadeOutAnimator.addListener(new AnimatorListenerAdapter() { 353 @Override 354 public void onAnimationEnd(Animator animation) { 355 stopPulsing(); 356 } 357 }); 358 } 359 360 @Override 361 public void setInnerAlpha(final int alpha) { 362 super.setInnerAlpha((int) (alpha * mPulseAlphaFraction)); 363 } 364 365 public void setPulseEnabled(boolean enabled) { 366 mPulseEnabled = enabled; 367 if (!mPulseEnabled) { 368 stopPulsing(); 369 } 370 } 371 372 private void stopPulsing() { 373 if (mPulseAnimator != null) { 374 mPulseAnimator.cancel(); 375 mPulseAlphaFraction = 1f; 376 setInnerAlpha(getCurrentAlpha()); 377 } 378 } 379 380 @Override 381 public boolean setVisible(boolean visible) { 382 final boolean changed = super.setVisible(visible); 383 if (changed) { 384 if (isVisible()) { 385 // start 386 if (mPulseAnimator != null && mPulseEnabled) { 387 mPulseAnimator.start(); 388 } 389 } else { 390 // can't cancel the pulsing yet-- wait for the fade-out animation to end 391 // one exception: if alpha is already zero, there is no fade-out, so stop now 392 if (getCurrentAlpha() == 0) { 393 stopPulsing(); 394 } 395 } 396 } 397 return changed; 398 } 399 400 } 401 402 private static class Progress extends TileDrawable { 403 404 private final ValueAnimator mRotateAnimator; 405 406 public Progress(Drawable progress, Resources res, 407 int progressBarWidth, int progressBarHeight, int fadeOutDurationMs, 408 int tileColor) { 409 super(progress, progressBarWidth, progressBarHeight, tileColor, fadeOutDurationMs); 410 411 mRotateAnimator = ValueAnimator.ofInt(0, 10000) 412 .setDuration(res.getInteger(R.integer.bitmap_progress_animation_duration)); 413 mRotateAnimator.setInterpolator(new LinearInterpolator()); 414 mRotateAnimator.setRepeatCount(ValueAnimator.INFINITE); 415 mRotateAnimator.addUpdateListener(new AnimatorUpdateListener() { 416 @Override 417 public void onAnimationUpdate(ValueAnimator animation) { 418 setLevel((Integer) animation.getAnimatedValue()); 419 } 420 }); 421 mFadeOutAnimator.addListener(new AnimatorListenerAdapter() { 422 @Override 423 public void onAnimationEnd(Animator animation) { 424 if (mRotateAnimator != null) { 425 mRotateAnimator.cancel(); 426 } 427 } 428 }); 429 } 430 431 @Override 432 public boolean setVisible(boolean visible) { 433 final boolean changed = super.setVisible(visible); 434 if (changed) { 435 if (isVisible()) { 436 if (mRotateAnimator != null) { 437 mRotateAnimator.start(); 438 } 439 } else { 440 // can't cancel the rotate yet-- wait for the fade-out animation to end 441 // one exception: if alpha is already zero, there is no fade-out, so stop now 442 if (getCurrentAlpha() == 0 && mRotateAnimator != null) { 443 mRotateAnimator.cancel(); 444 } 445 } 446 } 447 return changed; 448 } 449 450 } 451} 452