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