FileDocCategorySizeDatePackage
FrameSequenceDrawable.javaAPI DocAndroid 5.1 API13014Thu Mar 12 22:22:48 GMT 2015android.support.rastermill

FrameSequenceDrawable

public class FrameSequenceDrawable extends android.graphics.drawable.Drawable implements Runnable, android.graphics.drawable.Animatable

Fields Summary
private static final Object
sLock
private static android.os.HandlerThread
sDecodingThread
private static android.os.Handler
sDecodingThreadHandler
private static BitmapProvider
sAllocatingBitmapProvider
public static final int
LOOP_ONCE
Loop only once.
public static final int
LOOP_INF
Loop continuously. The OnFinishedListener will never be called.
public static final int
LOOP_DEFAULT
Use loop count stored in source data, or LOOP_ONCE if not present.
private final FrameSequence
mFrameSequence
private final FrameSequence.State
mFrameSequenceState
private final android.graphics.Paint
mPaint
private final android.graphics.Rect
mSrcRect
private final Object
mLock
private final BitmapProvider
mBitmapProvider
private boolean
mDestroyed
private android.graphics.Bitmap
mFrontBitmap
private android.graphics.Bitmap
mBackBitmap
private static final int
STATE_SCHEDULED
private static final int
STATE_DECODING
private static final int
STATE_WAITING_TO_SWAP
private static final int
STATE_READY_TO_SWAP
private int
mState
private int
mCurrentLoop
private int
mLoopBehavior
private long
mLastSwap
private long
mNextSwap
private int
mNextFrameToDecode
private OnFinishedListener
mOnFinishedListener
private Runnable
mDecodeRunnable
Runs on decoding thread, only modifies mBackBitmap's pixels
private Runnable
mCallbackRunnable
Constructors Summary
public FrameSequenceDrawable(FrameSequence frameSequence)

        this(frameSequence, sAllocatingBitmapProvider);
    
public FrameSequenceDrawable(FrameSequence frameSequence, BitmapProvider bitmapProvider)

        if (frameSequence == null || bitmapProvider == null) throw new IllegalArgumentException();

        mFrameSequence = frameSequence;
        mFrameSequenceState = frameSequence.createState();
        final int width = frameSequence.getWidth();
        final int height = frameSequence.getHeight();

        mBitmapProvider = bitmapProvider;
        mFrontBitmap = acquireAndValidateBitmap(bitmapProvider, width, height);
        mBackBitmap = acquireAndValidateBitmap(bitmapProvider, width, height);
        mSrcRect = new Rect(0, 0, width, height);
        mPaint = new Paint();
        mPaint.setFilterBitmap(true);

        mLastSwap = 0;

        mNextFrameToDecode = -1;
        mFrameSequenceState.getFrame(0, mFrontBitmap, -1);
        initializeDecodingThread();
    
Methods Summary
private static android.graphics.BitmapacquireAndValidateBitmap(android.support.rastermill.FrameSequenceDrawable$BitmapProvider bitmapProvider, int minWidth, int minHeight)


        
                
        Bitmap bitmap = bitmapProvider.acquireBitmap(minWidth, minHeight);

        if (bitmap.getWidth() < minWidth
                || bitmap.getHeight() < minHeight
                || bitmap.getConfig() != Bitmap.Config.ARGB_8888) {
            throw new IllegalArgumentException("Invalid bitmap provided");
        }

        return bitmap;
    
private voidcheckDestroyedLocked()

        if (mDestroyed) {
            throw new IllegalStateException("Cannot perform operation on recycled drawable");
        }
    
private voiddestroy(android.support.rastermill.FrameSequenceDrawable$BitmapProvider bitmapProvider)

        if (bitmapProvider == null) {
            throw new IllegalStateException("BitmapProvider must be non-null");
        }

        Bitmap bitmapToReleaseA;
        Bitmap bitmapToReleaseB;
        synchronized (mLock) {
            checkDestroyedLocked();

            bitmapToReleaseA = mFrontBitmap;
            bitmapToReleaseB = mBackBitmap;

            mFrontBitmap = null;
            mBackBitmap = null;
            mDestroyed = true;
        }

        // For simplicity and safety, we don't destroy the state object here
        bitmapProvider.releaseBitmap(bitmapToReleaseA);
        bitmapProvider.releaseBitmap(bitmapToReleaseB);
    
public voiddestroy()
Marks the drawable as permanently recycled (and thus unusable), and releases any owned Bitmaps drawable to its BitmapProvider, if attached. If no BitmapProvider is attached to the drawable, recycle() is called on the Bitmaps.

        destroy(mBitmapProvider);
    
public voiddraw(android.graphics.Canvas canvas)

        synchronized (mLock) {
            checkDestroyedLocked();
            if (mState == STATE_WAITING_TO_SWAP) {
                // may have failed to schedule mark ready runnable,
                // so go ahead and swap if swapping is due
                if (mNextSwap - SystemClock.uptimeMillis() <= 0) {
                    mState = STATE_READY_TO_SWAP;
                }
            }

            if (isRunning() && mState == STATE_READY_TO_SWAP) {
                // Because draw has occurred, the view system is guaranteed to no longer hold a
                // reference to the old mFrontBitmap, so we now use it to produce the next frame
                Bitmap tmp = mBackBitmap;
                mBackBitmap = mFrontBitmap;
                mFrontBitmap = tmp;

                mLastSwap = SystemClock.uptimeMillis();

                boolean continueLooping = true;
                if (mNextFrameToDecode == mFrameSequence.getFrameCount() - 1) {
                    mCurrentLoop++;
                    if ((mLoopBehavior == LOOP_ONCE && mCurrentLoop == 1) ||
                            (mLoopBehavior == LOOP_DEFAULT && mCurrentLoop == mFrameSequence.getDefaultLoopCount())) {
                        continueLooping = false;
                    }
                }

                if (continueLooping) {
                    scheduleDecodeLocked();
                } else {
                    scheduleSelf(mCallbackRunnable, 0);
                }
            }
        }

        canvas.drawBitmap(mFrontBitmap, mSrcRect, getBounds(), mPaint);
    
protected voidfinalize()

        try {
            mFrameSequenceState.destroy();
            if (!mDestroyed) {
                destroy();
            }
        } finally {
            super.finalize();
        }
    
public intgetIntrinsicHeight()

        return mFrameSequence.getHeight();
    
public intgetIntrinsicWidth()

        return mFrameSequence.getWidth();
    
public intgetOpacity()

        return mFrameSequence.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSPARENT;
    
private static voidinitializeDecodingThread()

        
        synchronized (sLock) {
            if (sDecodingThread != null) return;

            sDecodingThread = new HandlerThread("FrameSequence decoding thread",
                    Process.THREAD_PRIORITY_BACKGROUND);
            sDecodingThread.start();
            sDecodingThreadHandler = new Handler(sDecodingThread.getLooper());
        }
    
public booleanisDestroyed()

        synchronized (mLock) {
            return mDestroyed;
        }
    
public booleanisRunning()

        synchronized (mLock) {
            return mNextFrameToDecode > -1 && !mDestroyed;
        }
    
public voidrun()

        // set ready to swap
        synchronized (mLock) {
            if (mState != STATE_WAITING_TO_SWAP || mNextFrameToDecode < 0) return;
            mState = STATE_READY_TO_SWAP;
        }
        invalidateSelf();
    
private voidscheduleDecodeLocked()

        mState = STATE_SCHEDULED;
        mNextFrameToDecode = (mNextFrameToDecode + 1) % mFrameSequence.getFrameCount();
        sDecodingThreadHandler.post(mDecodeRunnable);
    
public voidsetAlpha(int alpha)

        mPaint.setAlpha(alpha);
    
public voidsetColorFilter(android.graphics.ColorFilter colorFilter)

        mPaint.setColorFilter(colorFilter);
    
public voidsetFilterBitmap(boolean filter)

        mPaint.setFilterBitmap(filter);
    
public voidsetLoopBehavior(int loopBehavior)
Define looping behavior of frame sequence. Must be one of LOOP_ONCE, LOOP_INF, or LOOP_DEFAULT


                       
        
        mLoopBehavior = loopBehavior;
    
public voidsetOnFinishedListener(android.support.rastermill.FrameSequenceDrawable$OnFinishedListener onFinishedListener)
Register a callback to be invoked when a FrameSequenceDrawable finishes looping.

see
#setLoopBehavior(int)


                      
        
        mOnFinishedListener = onFinishedListener;
    
public booleansetVisible(boolean visible, boolean restart)

        boolean changed = super.setVisible(visible, restart);

        if (!visible) {
            stop();
        } else if (restart || changed) {
            stop();
            start();
        }

        return changed;
    
public voidstart()

        if (!isRunning()) {
            synchronized (mLock) {
                checkDestroyedLocked();
                if (mState == STATE_SCHEDULED) return; // already scheduled
                mCurrentLoop = 0;
                scheduleDecodeLocked();
            }
        }
    
public voidstop()

        if (isRunning()) {
            unscheduleSelf(this);
        }
    
public voidunscheduleSelf(java.lang.Runnable what)

        synchronized (mLock) {
            mNextFrameToDecode = -1;
        }
        super.unscheduleSelf(what);