ExtendedBitmapDrawable.java revision df3da61c8f2f54604376d9761649bdba54aa858b
15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/* 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Copyright (C) 2013 The Android Open Source Project 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Licensed under the Apache License, Version 2.0 (the "License"); 55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * you may not use this file except in compliance with the License. 65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * You may obtain a copy of the License at 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * http://www.apache.org/licenses/LICENSE-2.0 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Unless required by applicable law or agreed to in writing, software 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * distributed under the License is distributed on an "AS IS" BASIS, 125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * See the License for the specific language governing permissions and 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * limitations under the License. 152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */ 1658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)package com.android.bitmap.drawable; 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.animation.Animator; 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.animation.AnimatorListenerAdapter; 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.animation.ValueAnimator; 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.animation.ValueAnimator.AnimatorUpdateListener; 235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.content.res.Resources; 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.graphics.Canvas; 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.graphics.Color; 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.graphics.ColorFilter; 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.graphics.Rect; 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.graphics.drawable.Drawable; 295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.os.Handler; 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.util.Log; 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.view.animation.LinearInterpolator; 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.android.bitmap.BitmapCache; 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.android.bitmap.DecodeAggregator; 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.android.bitmap.DecodeTask; 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.android.bitmap.R; 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.android.bitmap.RequestKey; 385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.android.bitmap.ReusableBitmap; 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.android.bitmap.util.Trace; 405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * This class encapsulates all functionality needed to display a single image bitmap, 435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * including request creation/cancelling, data unbinding and re-binding, and fancy animations 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * to draw upon state changes. 455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * <p> 465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * The actual bitmap decode work is handled by {@link DecodeTask}. 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * TODO: have this class extend from BasicBitmapDrawable 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)public class ExtendedBitmapDrawable extends BasicBitmapDrawable implements 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Runnable, Parallaxable, DecodeAggregator.Callback { 51d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private final ExtendedOptions mOpts; 535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Parallax. 555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private static final float DECODE_VERTICAL_CENTER = 1f / 3; 565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private float mParallaxFraction = 1f / 2; 575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // State changes. 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private static final int LOAD_STATE_UNINITIALIZED = 0; 605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private static final int LOAD_STATE_NOT_YET_LOADED = 1; 615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private static final int LOAD_STATE_LOADING = 2; 625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private static final int LOAD_STATE_LOADED = 3; 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private static final int LOAD_STATE_FAILED = 4; 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private int mLoadState = LOAD_STATE_UNINITIALIZED; 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private Placeholder mPlaceholder; 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private Progress mProgress; 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private int mProgressDelayMs; 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private final Handler mHandler = new Handler(); 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public static final boolean DEBUG = false; 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public static final String TAG = ExtendedBitmapDrawable.class.getSimpleName(); 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public ExtendedBitmapDrawable(final Resources res, final BitmapCache cache, 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) final boolean limitDensity, final ExtendedOptions opts) { 755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) super(res, cache, limitDensity); 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) opts.validate(); 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) mOpts = opts; 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Placeholder and progress. 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if ((opts.features & ExtendedOptions.FEATURE_STATE_CHANGES) != 0) { 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) final int fadeOutDurationMs = res.getInteger(R.integer.bitmap_fade_animation_duration); 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) mProgressDelayMs = res.getInteger(R.integer.bitmap_progress_animation_delay); 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Placeholder is not optional because backgroundColor is part of it. 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Drawable placeholder = null; 875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (opts.placeholder != null) { 885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ConstantState constantState = opts.placeholder.getConstantState(); 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (constantState != null) { 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) placeholder = constantState.newDrawable(res); 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int placeholderSize = res.getDimensionPixelSize(R.dimen.placeholder_size); 945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) mPlaceholder = new Placeholder(placeholder, res, placeholderSize, placeholderSize, 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) fadeOutDurationMs, opts); 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) mPlaceholder.setCallback(this); 975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Progress bar is optional. 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (opts.progressBar != null) { 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int progressBarSize = res.getDimensionPixelSize(R.dimen.progress_bar_size); 101a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch mProgress = new Progress(opts.progressBar.getConstantState().newDrawable(res), res, 1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) progressBarSize, progressBarSize, fadeOutDurationMs, opts); 1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) mProgress.setCallback(this); 1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 105c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) } 1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @Override 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public void setParallaxFraction(float fraction) { 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) mParallaxFraction = fraction; 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) invalidateSelf(); 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Get the ExtendedOptions used to instantiate this ExtendedBitmapDrawable. Any changes made to 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * the parameters inside the options will take effect immediately. 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public ExtendedOptions getExtendedOptions() { 1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return mOpts; 1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * This sets the drawable to the failed state, which remove all animations from the placeholder. 1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * This is different from unbinding to the uninitialized state, where we expect animations. 1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public void showStaticPlaceholder() { 127cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) setLoadState(LOAD_STATE_FAILED); 1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @Override 131c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) protected void setImage(final RequestKey key) { 1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (mCurrKey != null && mCurrKey.equals(key)) { 1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (mCurrKey != null && getDecodeAggregator() != null) { 1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) getDecodeAggregator().forget(mCurrKey); 1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) mHandler.removeCallbacks(this); 1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // start from a clean slate on every bind 1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // this allows the initial transition to be specially instantaneous, so e.g. a cache hit 1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // doesn't unnecessarily trigger a fade-in 1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) setLoadState(LOAD_STATE_UNINITIALIZED); 1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (key == null) { 1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) setLoadState(LOAD_STATE_FAILED); 1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) super.setImage(key); 1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 151f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) 1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @Override 1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) protected void setBitmap(ReusableBitmap bmp) { 1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) setLoadState((bmp != null) ? LOAD_STATE_LOADED : LOAD_STATE_FAILED); 1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 156f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) super.setBitmap(bmp); 1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @Override 1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) protected void loadFileDescriptorFactory() { 1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) boolean executeStateChange = shouldExecuteStateChange(); 1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (executeStateChange) { 1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (mCurrKey == null || mDecodeWidth == 0 || mDecodeHeight == 0) { 1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) setLoadState(LOAD_STATE_FAILED); 1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) setLoadState(LOAD_STATE_NOT_YET_LOADED); 1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) super.loadFileDescriptorFactory(); 1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) protected boolean shouldExecuteStateChange() { 1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // TODO: AttachmentDrawable should override this method to match prev and curr request keys. 1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return /* opts.stateChanges */ true; 1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @Override 1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public float getDrawVerticalCenter() { 1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return mParallaxFraction; 1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @Override 1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) protected float getDrawVerticalOffsetMultiplier() { 1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return mOpts.parallaxSpeedMultiplier; 1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @Override 1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) protected float getDecodeVerticalCenter() { 1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return DECODE_VERTICAL_CENTER; 1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private DecodeAggregator getDecodeAggregator() { 1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return mOpts.decodeAggregator; 1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 19890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * Instead of overriding this method, subclasses should override {@link #onDraw(Canvas)}. 19990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * 20090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * The reason for this is that we need the placeholder and progress bar to be drawn over our 2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * content. Those two drawables fade out, giving the impression that our content is fading in. 2025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * 2035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Only override this method for custom drawings on top of all the drawable layers. 2045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @Override 2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public void draw(final Canvas canvas) { 2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) final Rect bounds = getBounds(); 2082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (bounds.isEmpty()) { 2092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return; 2102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 2112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) onDraw(canvas); 2135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 2142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Draw the two possible overlay layers in reverse-priority order. 2152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // (each layer will no-op the draw when appropriate) 2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // This ordering means cross-fade transitions are just fade-outs of each layer. 2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (mProgress != null) mProgress.draw(canvas); 2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (mPlaceholder != null) mPlaceholder.draw(canvas); 2192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 2202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 2222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * Overriding this method to add your own custom drawing. 2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 2242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) protected void onDraw(final Canvas canvas) { 2252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) super.draw(canvas); 2262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 2272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) @Override 2292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) public void setAlpha(int alpha) { 2302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) final int old = mPaint.getAlpha(); 2312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) super.setAlpha(alpha); 2322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (mPlaceholder != null) mPlaceholder.setAlpha(alpha); 2332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (mProgress != null) mProgress.setAlpha(alpha); 2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (alpha != old) { 2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) invalidateSelf(); 2362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @Override 2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public void setColorFilter(ColorFilter cf) { 2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) super.setColorFilter(cf); 2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (mPlaceholder != null) mPlaceholder.setColorFilter(cf); 2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (mProgress != null) mProgress.setColorFilter(cf); 2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) invalidateSelf(); 2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @Override 2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) protected void onBoundsChange(Rect bounds) { 2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) super.onBoundsChange(bounds); 2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (mPlaceholder != null) mPlaceholder.setBounds(bounds); 2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (mProgress != null) mProgress.setBounds(bounds); 2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @Override 2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public void onDecodeBegin(final RequestKey key) { 25690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) if (getDecodeAggregator() != null) { 25790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) getDecodeAggregator().expect(key, this); 25890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } else { 2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) onBecomeFirstExpected(key); 2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) super.onDecodeBegin(key); 2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @Override 2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public void onBecomeFirstExpected(final RequestKey key) { 266 if (!key.equals(mCurrKey)) { 267 return; 268 } 269 // normally, we'd transition to the LOADING state now, but we want to delay that a bit 270 // to minimize excess occurrences of the rotating spinner 271 mHandler.postDelayed(this, mProgressDelayMs); 272 } 273 274 @Override 275 public void run() { 276 if (mLoadState == LOAD_STATE_NOT_YET_LOADED) { 277 setLoadState(LOAD_STATE_LOADING); 278 } 279 } 280 281 @Override 282 public void onDecodeComplete(final RequestKey key, final ReusableBitmap result) { 283 if (getDecodeAggregator() != null) { 284 getDecodeAggregator().execute(key, new Runnable() { 285 @Override 286 public void run() { 287 ExtendedBitmapDrawable.super.onDecodeComplete(key, result); 288 } 289 290 @Override 291 public String toString() { 292 return "DONE"; 293 } 294 }); 295 } else { 296 super.onDecodeComplete(key, result); 297 } 298 } 299 300 @Override 301 public void onDecodeCancel(final RequestKey key) { 302 if (getDecodeAggregator() != null) { 303 getDecodeAggregator().forget(key); 304 } 305 super.onDecodeCancel(key); 306 } 307 308 /** 309 * Each attachment gets its own placeholder and progress indicator, to be shown, hidden, 310 * and animated based on Drawable#setVisible() changes, which are in turn driven by 311 * setLoadState(). 312 */ 313 private void setLoadState(int loadState) { 314 if (DEBUG) { 315 Log.v(TAG, String.format("IN setLoadState. old=%s new=%s key=%s this=%s", 316 mLoadState, loadState, mCurrKey, this)); 317 } 318 if (mLoadState == loadState) { 319 if (DEBUG) { 320 Log.v(TAG, "OUT no-op setLoadState"); 321 } 322 return; 323 } 324 325 Trace.beginSection("set load state"); 326 switch (loadState) { 327 // This state differs from LOADED in that the subsequent state transition away from 328 // UNINITIALIZED will not have a fancy transition. This allows list item binds to 329 // cached data to take immediate effect without unnecessary whizzery. 330 case LOAD_STATE_UNINITIALIZED: 331 if (mPlaceholder != null) mPlaceholder.reset(); 332 if (mProgress != null) mProgress.reset(); 333 break; 334 case LOAD_STATE_NOT_YET_LOADED: 335 if (mPlaceholder != null) { 336 mPlaceholder.setPulseEnabled(true); 337 mPlaceholder.setVisible(true); 338 } 339 if (mProgress != null) mProgress.setVisible(false); 340 break; 341 case LOAD_STATE_LOADING: 342 if (mProgress == null) { 343 // Stay in same visual state as LOAD_STATE_NOT_YET_LOADED. 344 break; 345 } 346 if (mPlaceholder != null) mPlaceholder.setVisible(false); 347 if (mProgress != null) mProgress.setVisible(true); 348 break; 349 case LOAD_STATE_LOADED: 350 if (mPlaceholder != null) mPlaceholder.setVisible(false); 351 if (mProgress != null) mProgress.setVisible(false); 352 break; 353 case LOAD_STATE_FAILED: 354 if (mPlaceholder != null) { 355 mPlaceholder.setPulseEnabled(false); 356 mPlaceholder.setVisible(true); 357 } 358 if (mProgress != null) mProgress.setVisible(false); 359 break; 360 } 361 Trace.endSection(); 362 363 mLoadState = loadState; 364 boolean placeholderVisible = mPlaceholder != null && mPlaceholder.isVisible(); 365 boolean progressVisible = mProgress != null && mProgress.isVisible(); 366 367 if (DEBUG) { 368 Log.v(TAG, String.format("OUT stateful setLoadState. new=%s placeholder=%s progress=%s", 369 loadState, placeholderVisible, progressVisible)); 370 } 371 } 372 373 private static class Placeholder extends TileDrawable { 374 375 private final ValueAnimator mPulseAnimator; 376 private boolean mPulseEnabled = true; 377 private float mPulseAlphaFraction = 1f; 378 379 public Placeholder(Drawable placeholder, Resources res, int placeholderWidth, 380 int placeholderHeight, int fadeOutDurationMs, ExtendedOptions opts) { 381 super(placeholder, placeholderWidth, placeholderHeight, fadeOutDurationMs, opts); 382 383 if (opts.placeholderAnimationDuration == -1) { 384 mPulseAnimator = null; 385 } else { 386 final long pulseDuration; 387 if (opts.placeholderAnimationDuration == 0) { 388 pulseDuration = res.getInteger(R.integer.bitmap_placeholder_animation_duration); 389 } else { 390 pulseDuration = opts.placeholderAnimationDuration; 391 } 392 mPulseAnimator = ValueAnimator.ofInt(55, 255).setDuration(pulseDuration); 393 mPulseAnimator.setRepeatCount(ValueAnimator.INFINITE); 394 mPulseAnimator.setRepeatMode(ValueAnimator.REVERSE); 395 mPulseAnimator.addUpdateListener(new AnimatorUpdateListener() { 396 @Override 397 public void onAnimationUpdate(ValueAnimator animation) { 398 mPulseAlphaFraction = ((Integer) animation.getAnimatedValue()) / 255f; 399 setInnerAlpha(getCurrentAlpha()); 400 } 401 }); 402 } 403 mFadeOutAnimator.addListener(new AnimatorListenerAdapter() { 404 @Override 405 public void onAnimationEnd(Animator animation) { 406 stopPulsing(); 407 } 408 }); 409 } 410 411 @Override 412 public void setInnerAlpha(final int alpha) { 413 super.setInnerAlpha((int) (alpha * mPulseAlphaFraction)); 414 } 415 416 public void setPulseEnabled(boolean enabled) { 417 mPulseEnabled = enabled; 418 if (!mPulseEnabled) { 419 stopPulsing(); 420 } else { 421 startPulsing(); 422 } 423 } 424 425 private void stopPulsing() { 426 if (mPulseAnimator != null) { 427 mPulseAnimator.cancel(); 428 mPulseAlphaFraction = 1f; 429 setInnerAlpha(getCurrentAlpha()); 430 } 431 } 432 433 private void startPulsing() { 434 if (mPulseAnimator != null && !mPulseAnimator.isStarted()) { 435 mPulseAnimator.start(); 436 } 437 } 438 439 @Override 440 public boolean setVisible(boolean visible) { 441 final boolean changed = super.setVisible(visible); 442 if (changed) { 443 if (isVisible()) { 444 // start 445 if (mPulseAnimator != null && mPulseEnabled && !mPulseAnimator.isStarted()) { 446 mPulseAnimator.start(); 447 } 448 } else { 449 // can't cancel the pulsing yet-- wait for the fade-out animation to end 450 // one exception: if alpha is already zero, there is no fade-out, so stop now 451 if (getCurrentAlpha() == 0) { 452 stopPulsing(); 453 } 454 } 455 } 456 return changed; 457 } 458 459 } 460 461 private static class Progress extends TileDrawable { 462 463 private final ValueAnimator mRotateAnimator; 464 465 public Progress(Drawable progress, Resources res, 466 int progressBarWidth, int progressBarHeight, int fadeOutDurationMs, 467 ExtendedOptions opts) { 468 super(progress, progressBarWidth, progressBarHeight, fadeOutDurationMs, opts); 469 470 mRotateAnimator = ValueAnimator.ofInt(0, 10000) 471 .setDuration(res.getInteger(R.integer.bitmap_progress_animation_duration)); 472 mRotateAnimator.setInterpolator(new LinearInterpolator()); 473 mRotateAnimator.setRepeatCount(ValueAnimator.INFINITE); 474 mRotateAnimator.addUpdateListener(new AnimatorUpdateListener() { 475 @Override 476 public void onAnimationUpdate(ValueAnimator animation) { 477 setLevel((Integer) animation.getAnimatedValue()); 478 } 479 }); 480 mFadeOutAnimator.addListener(new AnimatorListenerAdapter() { 481 @Override 482 public void onAnimationEnd(Animator animation) { 483 if (mRotateAnimator != null) { 484 mRotateAnimator.cancel(); 485 } 486 } 487 }); 488 } 489 490 @Override 491 public boolean setVisible(boolean visible) { 492 final boolean changed = super.setVisible(visible); 493 if (changed) { 494 if (isVisible()) { 495 if (mRotateAnimator != null) { 496 mRotateAnimator.start(); 497 } 498 } else { 499 // can't cancel the rotate yet-- wait for the fade-out animation to end 500 // one exception: if alpha is already zero, there is no fade-out, so stop now 501 if (getCurrentAlpha() == 0 && mRotateAnimator != null) { 502 mRotateAnimator.cancel(); 503 } 504 } 505 } 506 return changed; 507 } 508 } 509 510 /** 511 * This class contains the features a client can specify, and arguments to those features. 512 * Clients can later retrieve the ExtendedOptions from an ExtendedBitmapDrawable and change the 513 * parameters, which will be reflected immediately. 514 */ 515 public static class ExtendedOptions { 516 517 /** 518 * Summary: 519 * This feature enables you to draw decoded bitmap in order on the screen, to give the 520 * visual effect of a single decode thread. 521 * 522 * <p/> 523 * Explanation: 524 * Since DecodeTasks are asynchronous, multiple tasks may finish decoding at different 525 * times. To have a smooth user experience, provide a shared {@link DecodeAggregator} to all 526 * the ExtendedBitmapDrawables, and the decode aggregator will hold finished decodes so they 527 * come back in order. 528 * 529 * <p/> 530 * Pros: 531 * Visual consistency. Images are not popping up randomly all over the place. 532 * 533 * <p/> 534 * Cons: 535 * Artificial delay. Images are not drawn as soon as they are decoded. They must wait 536 * for their turn. 537 * 538 * <p/> 539 * Requirements: 540 * Set {@link #decodeAggregator} to a shared {@link DecodeAggregator}. 541 */ 542 public static final int FEATURE_ORDERED_DISPLAY = 1; 543 544 /** 545 * Summary: 546 * This feature enables the image to move in parallax as the user scrolls, to give visual 547 * flair to your images. 548 * 549 * <p/> 550 * Explanation: 551 * When the user scrolls D pixels in the vertical direction, this ExtendedBitmapDrawable 552 * shifts its Bitmap f(D) pixels in the vertical direction before drawing to the screen. 553 * Depending on the function f, the parallax effect can give varying interesting results. 554 * 555 * <p/> 556 * Pros: 557 * Visual pop and playfulness. Feeling of movement. Pleasantly surprise your users. 558 * 559 * <p/> 560 * Cons: 561 * Some users report motion sickness with certain speed multiplier values. Decode height 562 * must be greater than visual bounds to account for the parallax. This uses more memory and 563 * decoding time. 564 * 565 * <p/> 566 * Requirements: 567 * Set {@link #parallaxSpeedMultiplier} to the ratio between the decoded height and the 568 * visual bound height. Call {@link ExtendedBitmapDrawable#setDecodeDimensions(int, int)} 569 * with the height multiplied by {@link #parallaxSpeedMultiplier}. 570 * Call {@link ExtendedBitmapDrawable#setParallaxFraction(float)} when the user scrolls. 571 */ 572 public static final int FEATURE_PARALLAX = 1 << 1; 573 574 /** 575 * Summary: 576 * This feature enables fading in between multiple decode states, to give smooth transitions 577 * to and from the placeholder, progress bars, and decoded image. 578 * 579 * <p/> 580 * Explanation: 581 * The states are: {@link ExtendedBitmapDrawable#LOAD_STATE_UNINITIALIZED}, 582 * {@link ExtendedBitmapDrawable#LOAD_STATE_NOT_YET_LOADED}, 583 * {@link ExtendedBitmapDrawable#LOAD_STATE_LOADING}, 584 * {@link ExtendedBitmapDrawable#LOAD_STATE_LOADED}, and 585 * {@link ExtendedBitmapDrawable#LOAD_STATE_FAILED}. These states affect whether the 586 * placeholder and/or the progress bar is showing and animating. We first show the 587 * pulsating placeholder when an image begins decoding. After 2 seconds, we fade in a 588 * spinning progress bar. When the decode completes, we fade in the image. 589 * 590 * <p/> 591 * Pros: 592 * Smooth, beautiful transitions avoid perceived jank. Progress indicator informs users that 593 * work is being done and the app is not stalled. 594 * 595 * <p/> 596 * Cons: 597 * Very fast decodes' short decode time would be eclipsed by the animation duration. Static 598 * placeholder could be accomplished by {@link BasicBitmapDrawable} without the added 599 * complexity of states. 600 * 601 * <p/> 602 * Requirements: 603 * Set {@link #backgroundColor} to the color used for the background of the placeholder and 604 * progress bar. Use the alternative constructor to populate {@link #placeholder} and 605 * {@link #progressBar}. Optionally set {@link #placeholderAnimationDuration}. 606 */ 607 public static final int FEATURE_STATE_CHANGES = 1 << 2; 608 609 /** 610 * Non-changeable bit field describing the features you want the 611 * {@link ExtendedBitmapDrawable} to support. 612 * 613 * <p/> 614 * Example: 615 * <code> 616 * opts.features = FEATURE_ORDERED_DISPLAY | FEATURE_PARALLAX | FEATURE_STATE_CHANGES; 617 * </code> 618 */ 619 public final int features; 620 621 /** 622 * Required field if {@link #FEATURE_ORDERED_DISPLAY} is supported. 623 */ 624 public DecodeAggregator decodeAggregator = null; 625 626 /** 627 * Required field if {@link #FEATURE_PARALLAX} is supported. 628 * 629 * A value of 1.5f gives a subtle parallax, and is a good value to 630 * start with. 2.0f gives a more obvious parallax, arguably exaggerated. Some users report 631 * motion sickness with 2.0f. A value of 1.0f is synonymous with no parallax. Be careful not 632 * to set too high a value, since we will start cropping the widths if the image's height is 633 * not sufficient. 634 */ 635 public float parallaxSpeedMultiplier = 1; 636 637 /** 638 * Optional field if {@link #FEATURE_STATE_CHANGES} is supported. Must be an opaque color. 639 * 640 * See {@link android.graphics.Color}. 641 */ 642 public int backgroundColor = 0; 643 644 /** 645 * Optional non-changeable field if {@link #FEATURE_STATE_CHANGES} is supported. 646 */ 647 public final Drawable placeholder; 648 649 /** 650 * Optional field if {@link #FEATURE_STATE_CHANGES} is supported. 651 * 652 * Special value 0 means default animation duration. Special value -1 means disable the 653 * animation (placeholder will be at maximum alpha always). Any value > 0 defines the 654 * duration in milliseconds. 655 */ 656 public int placeholderAnimationDuration = 0; 657 658 /** 659 * Optional non-changeable field if {@link #FEATURE_STATE_CHANGES} is supported. 660 */ 661 public final Drawable progressBar; 662 663 /** 664 * Use this constructor when all the feature parameters are changeable. 665 */ 666 public ExtendedOptions(final int features) { 667 this(features, null, null); 668 } 669 670 /** 671 * Use this constructor when you have to specify non-changeable feature parameters. 672 */ 673 public ExtendedOptions(final int features, final Drawable placeholder, 674 final Drawable progressBar) { 675 this.features = features; 676 this.placeholder = placeholder; 677 this.progressBar = progressBar; 678 } 679 680 /** 681 * Validate this ExtendedOptions instance to make sure that all the required fields are set 682 * for the requested features. 683 * 684 * This will throw an IllegalStateException if validation fails. 685 */ 686 private void validate() 687 throws IllegalStateException { 688 if ((features & FEATURE_ORDERED_DISPLAY) != 0 && decodeAggregator == null) { 689 throw new IllegalStateException( 690 "ExtendedOptions: To support FEATURE_ORDERED_DISPLAY, " 691 + "decodeAggregator must be set."); 692 } 693 if ((features & FEATURE_PARALLAX) != 0 && parallaxSpeedMultiplier <= 1) { 694 throw new IllegalStateException( 695 "ExtendedOptions: To support FEATURE_PARALLAX, " 696 + "parallaxSpeedMultiplier must be greater than 1."); 697 } 698 if ((features & FEATURE_STATE_CHANGES) != 0) { 699 if (backgroundColor == 0 700 && placeholder == null) { 701 throw new IllegalStateException( 702 "ExtendedOptions: To support FEATURE_STATE_CHANGES, " 703 + "either backgroundColor or placeholder must be set."); 704 } 705 if (placeholderAnimationDuration < -1) { 706 throw new IllegalStateException( 707 "ExtendedOptions: To support FEATURE_STATE_CHANGES, " 708 + "placeholderAnimationDuration must be set correctly."); 709 } 710 if (backgroundColor != 0 && Color.alpha(backgroundColor) != 255) { 711 throw new IllegalStateException( 712 "ExtendedOptions: To support FEATURE_STATE_CHANGES, " 713 + "backgroundColor must be set to an opaque color."); 714 } 715 } 716 } 717 } 718} 719