FrameSequenceDrawable.java revision e36c5d675c8c2f900ef186a55edf71ce36ca9fa0
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.rastermill; 18 19import android.graphics.Bitmap; 20import android.graphics.Canvas; 21import android.graphics.ColorFilter; 22import android.graphics.Paint; 23import android.graphics.PixelFormat; 24import android.graphics.Rect; 25import android.graphics.drawable.Animatable; 26import android.graphics.drawable.Drawable; 27import android.os.Handler; 28import android.os.HandlerThread; 29import android.os.SystemClock; 30 31public class FrameSequenceDrawable extends Drawable implements Animatable, Runnable { 32 private static final Object sLock = new Object(); 33 private static HandlerThread sDecodingThread; 34 private static Handler sDecodingThreadHandler; 35 private static void initializeDecodingThread() { 36 synchronized (sLock) { 37 if (sDecodingThread != null) return; 38 39 sDecodingThread = new HandlerThread("FrameSequence decoding thread"); 40 sDecodingThread.start(); 41 sDecodingThreadHandler = new Handler(sDecodingThread.getLooper()); 42 } 43 } 44 45 public static interface OnFinishedListener { 46 /** 47 * Called when a FrameSequenceDrawable has finished looping. 48 * 49 * Note that this is will not be called if the drawable is explicitly 50 * stopped, or marked invisible. 51 */ 52 public abstract void onFinished(FrameSequenceDrawable drawable); 53 } 54 55 /** 56 * Register a callback to be invoked when a FrameSequenceDrawable finishes looping. 57 * 58 * @see setLoopBehavior() 59 */ 60 public void setOnFinishedListener(OnFinishedListener onFinishedListener) { 61 mOnFinishedListener = onFinishedListener; 62 } 63 64 /** 65 * Loop only once. 66 */ 67 public static final int LOOP_ONCE = 1; 68 69 /** 70 * Loop continuously. The OnFinishedListener will never be called. 71 */ 72 public static final int LOOP_INF = 2; 73 74 /** 75 * Use loop count stored in source data, or LOOP_ONCE if not present. 76 */ 77 public static final int LOOP_DEFAULT = 3; 78 79 /** 80 * Define looping behavior of frame sequence. 81 * 82 * Must be one of LOOP_ONCE, LOOP_INF, or LOOP_DEFAULT 83 */ 84 public void setLoopBehavior(int loopBehavior) { 85 mLoopBehavior = loopBehavior; 86 } 87 88 private final FrameSequence mFrameSequence; 89 private final FrameSequence.State mFrameSequenceState; 90 91 private final Paint mPaint; 92 private final Rect mSrcRect; 93 94 //Protects the fields below 95 private final Object mLock = new Object(); 96 97 private Bitmap mFrontBitmap; 98 private Bitmap mBackBitmap; 99 100 private static final int STATE_SCHEDULED = 1; 101 private static final int STATE_DECODING = 2; 102 private static final int STATE_WAITING_TO_SWAP = 3; 103 private static final int STATE_READY_TO_SWAP = 4; 104 105 private int mState; 106 private int mCurrentLoop; 107 private int mLoopBehavior = LOOP_DEFAULT; 108 109 private long mLastSwap; 110 private int mNextFrameToDecode; 111 private OnFinishedListener mOnFinishedListener; 112 113 /** 114 * Runs on decoding thread, only modifies mBackBitmap's pixels 115 */ 116 private Runnable mDecodeRunnable = new Runnable() { 117 @Override 118 public void run() { 119 int nextFrame; 120 Bitmap bitmap; 121 synchronized (mLock) { 122 nextFrame = mNextFrameToDecode; 123 if (nextFrame < 0) { 124 return; 125 } 126 bitmap = mBackBitmap; 127 mState = STATE_DECODING; 128 } 129 int lastFrame = nextFrame - 2; 130 long invalidateTimeMs = mFrameSequenceState.getFrame(nextFrame, bitmap, lastFrame); 131 132 synchronized (mLock) { 133 if (mNextFrameToDecode < 0 || mState != STATE_DECODING) return; 134 invalidateTimeMs += mLastSwap; 135 136 mState = STATE_WAITING_TO_SWAP; 137 } 138 scheduleSelf(FrameSequenceDrawable.this, invalidateTimeMs); 139 } 140 }; 141 142 private Runnable mCallbackRunnable = new Runnable() { 143 @Override 144 public void run() { 145 if (mOnFinishedListener != null) { 146 mOnFinishedListener.onFinished(FrameSequenceDrawable.this); 147 } 148 } 149 }; 150 151 public FrameSequenceDrawable(FrameSequence frameSequence) { 152 if (frameSequence == null) throw new IllegalArgumentException(); 153 154 mFrameSequence = frameSequence; 155 mFrameSequenceState = frameSequence.createState(); 156 // TODO: add callback for requesting bitmaps, to allow for reuse 157 final int width = frameSequence.getWidth(); 158 final int height = frameSequence.getHeight(); 159 160 mFrontBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 161 mBackBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 162 mSrcRect = new Rect(0, 0, width, height); 163 mPaint = new Paint(); 164 mPaint.setFilterBitmap(true); 165 166 mLastSwap = 0; 167 168 mNextFrameToDecode = -1; 169 mFrameSequenceState.getFrame(0, mFrontBitmap, -1); 170 initializeDecodingThread(); 171 } 172 173 @Override 174 protected void finalize() throws Throwable { 175 try { 176 mFrontBitmap.recycle(); 177 mBackBitmap.recycle(); 178 mFrameSequenceState.recycle(); 179 } finally { 180 super.finalize(); 181 } 182 } 183 184 @Override 185 public void draw(Canvas canvas) { 186 synchronized (mLock) { 187 if (isRunning() && mState == STATE_READY_TO_SWAP) { 188 // Because draw has occurred, the view system is guaranteed to no longer hold a 189 // reference to the old mFrontBitmap, so we now use it to produce the next frame 190 Bitmap tmp = mBackBitmap; 191 mBackBitmap = mFrontBitmap; 192 mFrontBitmap = tmp; 193 194 mLastSwap = SystemClock.uptimeMillis(); 195 196 boolean continueLooping = true; 197 if (mNextFrameToDecode == mFrameSequence.getFrameCount() - 1) { 198 mCurrentLoop++; 199 if ((mLoopBehavior == LOOP_ONCE && mCurrentLoop == 1) || 200 (mLoopBehavior == LOOP_DEFAULT && mCurrentLoop == mFrameSequence.getDefaultLoopCount())) { 201 continueLooping = false; 202 } 203 } 204 205 if (continueLooping) { 206 scheduleDecodeLocked(); 207 } else { 208 scheduleSelf(mCallbackRunnable, 0); 209 } 210 } 211 } 212 213 canvas.drawBitmap(mFrontBitmap, mSrcRect, getBounds(), mPaint); 214 } 215 216 private void scheduleDecodeLocked() { 217 mState = STATE_SCHEDULED; 218 mNextFrameToDecode = (mNextFrameToDecode + 1) % mFrameSequence.getFrameCount(); 219 sDecodingThreadHandler.post(mDecodeRunnable); 220 } 221 222 @Override 223 public void run() { 224 // set ready to swap 225 synchronized (mLock) { 226 if (mState != STATE_WAITING_TO_SWAP || mNextFrameToDecode < 0) return; 227 mState = STATE_READY_TO_SWAP; 228 } 229 invalidateSelf(); 230 } 231 232 @Override 233 public void start() { 234 if (!isRunning()) { 235 synchronized (mLock) { 236 if (mState == STATE_SCHEDULED) return; // already scheduled 237 mCurrentLoop = 0; 238 scheduleDecodeLocked(); 239 } 240 } 241 } 242 243 @Override 244 public void stop() { 245 if (isRunning()) { 246 unscheduleSelf(this); 247 } 248 } 249 250 @Override 251 public boolean isRunning() { 252 synchronized (mLock) { 253 return mNextFrameToDecode > -1; 254 } 255 } 256 257 @Override 258 public void scheduleSelf(Runnable what, long when) { 259 super.scheduleSelf(what, when); 260 } 261 262 @Override 263 public void unscheduleSelf(Runnable what) { 264 synchronized (mLock) { 265 mNextFrameToDecode = -1; 266 } 267 super.unscheduleSelf(what); 268 } 269 270 @Override 271 public boolean setVisible(boolean visible, boolean restart) { 272 boolean changed = super.setVisible(visible, restart); 273 274 if (!visible) { 275 stop(); 276 } else if (restart || changed) { 277 stop(); 278 start(); 279 } 280 281 return changed; 282 } 283 284 // drawing properties 285 286 @Override 287 public void setFilterBitmap(boolean filter) { 288 mPaint.setFilterBitmap(filter); 289 } 290 291 @Override 292 public void setAlpha(int alpha) { 293 mPaint.setAlpha(alpha); 294 } 295 296 @Override 297 public void setColorFilter(ColorFilter colorFilter) { 298 mPaint.setColorFilter(colorFilter); 299 } 300 301 @Override 302 public int getIntrinsicWidth() { 303 return mFrameSequence.getWidth(); 304 } 305 306 @Override 307 public int getIntrinsicHeight() { 308 return mFrameSequence.getHeight(); 309 } 310 311 @Override 312 public int getOpacity() { 313 return mFrameSequence.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSPARENT; 314 } 315} 316