GifDrawable.java revision e7319b67364bd0ac6306bc7470a43d4a31600c1a
1package com.bumptech.glide.load.resource.gif; 2 3import android.annotation.TargetApi; 4import android.content.Context; 5import android.content.res.Resources; 6import android.graphics.Bitmap; 7import android.graphics.Canvas; 8import android.graphics.ColorFilter; 9import android.graphics.Paint; 10import android.graphics.PixelFormat; 11import android.graphics.drawable.Drawable; 12import android.os.Build; 13 14import com.bumptech.glide.gifdecoder.GifDecoder; 15import com.bumptech.glide.gifdecoder.GifHeader; 16import com.bumptech.glide.load.Transformation; 17import com.bumptech.glide.load.resource.drawable.GlideDrawable; 18 19/** 20 * An animated {@link android.graphics.drawable.Drawable} that plays the frames of an animated GIF. 21 */ 22public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameCallback { 23 private final Paint paint = new Paint(); 24 private final GifFrameManager frameManager; 25 private final GifState state; 26 private final GifDecoder decoder; 27 28 /** The current frame to draw, or null if no frame has been loaded yet. */ 29 private Bitmap currentFrame; 30 /** True if the drawable is currently animating. */ 31 private boolean isRunning; 32 /** True if the drawable should animate while visible. */ 33 private boolean isStarted; 34 /** True if the drawable's resources have been recycled. */ 35 private boolean isRecycled; 36 /** True if the drawable is currently visible. */ 37 private boolean isVisible; 38 /** The number of times we've looped over all the frames in the gif. */ 39 private int loopCount; 40 /** The number of times to loop through the gif animation. */ 41 private int maxLoopCount = LOOP_FOREVER; 42 43 /** 44 * Constructor for GifDrawable. 45 * 46 * @see #setFrameTransformation(com.bumptech.glide.load.Transformation, int, int) 47 * 48 * @param context A context. 49 * @param bitmapProvider An {@link com.bumptech.glide.gifdecoder.GifDecoder.BitmapProvider} that can be used to 50 * retrieve re-usable {@link android.graphics.Bitmap}s. 51 * @param frameTransformation An {@link com.bumptech.glide.load.Transformation} that can be applied to each frame. 52 * @param targetFrameWidth The desired width of the frames displayed by this drawable (the width of the view or 53 * {@link com.bumptech.glide.request.target.Target} this drawable is being loaded into). 54 * @param targetFrameHeight The desired height of the frames displayed by this drawable (the height of the view or 55 * {@link com.bumptech.glide.request.target.Target} this drawable is being loaded into). 56 * @param id An id that uniquely identifies this particular gif. 57 * @param gifHeader The header data for this gif. 58 * @param data The full bytes of the gif. 59 * @param finalFrameWidth The final width of the frames displayed by this drawable after they have been transformed. 60 * @param finalFrameHeight The final height of the frames displayed by this drwaable after they have been 61 * transformed. 62 */ 63 public GifDrawable(Context context, GifDecoder.BitmapProvider bitmapProvider, 64 Transformation<Bitmap> frameTransformation, int targetFrameWidth, int targetFrameHeight, String id, 65 GifHeader gifHeader, byte[] data, int finalFrameWidth, int finalFrameHeight) { 66 this(new GifState(id, gifHeader, data, context, frameTransformation, targetFrameWidth, targetFrameHeight, 67 bitmapProvider, finalFrameWidth, finalFrameHeight)); 68 } 69 70 private GifDrawable(GifState state) { 71 this.state = state; 72 this.decoder = new GifDecoder(state.bitmapProvider); 73 decoder.setData(state.id, state.gifHeader, state.data); 74 frameManager = new GifFrameManager(state.context, decoder, state.frameTransformation, state.targetWidth, 75 state.targetHeight, state.finalFrameWidth, state.finalFrameHeight); 76 } 77 78 // For testing. 79 GifDrawable(GifDecoder decoder, GifFrameManager frameManager, int finalFrameWidth, int finalFrameHeight) { 80 this.decoder = decoder; 81 this.frameManager = frameManager; 82 this.state = new GifState(null); 83 state.finalFrameWidth = finalFrameWidth; 84 state.finalFrameHeight = finalFrameHeight; 85 } 86 87 public void setFrameTransformation(Transformation<Bitmap> frameTransformation, int finalFrameWidth, 88 int finalFrameHeight) { 89 state.frameTransformation = frameTransformation; 90 state.finalFrameWidth = finalFrameWidth; 91 state.finalFrameHeight = finalFrameHeight; 92 } 93 94 public Transformation<Bitmap> getFrameTransformation() { 95 return state.frameTransformation; 96 } 97 98 public byte[] getData() { 99 return state.data; 100 } 101 102 private void resetLoopCount() { 103 loopCount = 0; 104 } 105 106 @Override 107 public void start() { 108 isStarted = true; 109 resetLoopCount(); 110 if (isVisible) { 111 startRunning(); 112 } 113 } 114 115 @Override 116 public void stop() { 117 isStarted = false; 118 stopRunning(); 119 } 120 121 private void startRunning() { 122 if (!isRunning) { 123 isRunning = true; 124 frameManager.getNextFrame(this); 125 invalidateSelf(); 126 } 127 } 128 129 private void stopRunning() { 130 isRunning = false; 131 } 132 133 @Override 134 public boolean setVisible(boolean visible, boolean restart) { 135 isVisible = visible; 136 if (!visible) { 137 stopRunning(); 138 } else if (isStarted) { 139 startRunning(); 140 } 141 return super.setVisible(visible, restart); 142 } 143 144 @Override 145 public int getIntrinsicWidth() { 146 return state.finalFrameWidth; 147 } 148 149 @Override 150 public int getIntrinsicHeight() { 151 return state.finalFrameHeight; 152 } 153 154 @Override 155 public boolean isRunning() { 156 return isRunning; 157 } 158 159 // For testing. 160 void setIsRunning(boolean isRunning) { 161 this.isRunning = isRunning; 162 } 163 164 @Override 165 public void draw(Canvas canvas) { 166 if (currentFrame != null) { 167 canvas.drawBitmap(currentFrame, 0, 0, paint); 168 } 169 } 170 171 @Override 172 public void setAlpha(int i) { 173 paint.setAlpha(i); 174 } 175 176 @Override 177 public void setColorFilter(ColorFilter colorFilter) { 178 paint.setColorFilter(colorFilter); 179 } 180 181 @Override 182 public int getOpacity() { 183 return decoder.isTransparent() ? PixelFormat.TRANSPARENT : PixelFormat.OPAQUE; 184 } 185 186 @TargetApi(Build.VERSION_CODES.HONEYCOMB) 187 @Override 188 public void onFrameRead(Bitmap frame, int frameIndex) { 189 if (Build.VERSION_CODES.HONEYCOMB <= Build.VERSION.SDK_INT && getCallback() == null) { 190 stop(); 191 return; 192 } 193 if (!isRunning) { 194 return; 195 } 196 197 if (frame != null) { 198 currentFrame = frame; 199 invalidateSelf(); 200 } 201 202 if (frameIndex == decoder.getFrameCount() - 1) { 203 loopCount++; 204 } 205 206 if (maxLoopCount != LOOP_FOREVER && loopCount >= maxLoopCount) { 207 stop(); 208 } else { 209 frameManager.getNextFrame(this); 210 } 211 } 212 213 @Override 214 public ConstantState getConstantState() { 215 return state; 216 } 217 218 /** 219 * Clears any resources for loading frames that are currently held on to by this object. 220 */ 221 public void recycle() { 222 isRecycled = true; 223 frameManager.clear(); 224 } 225 226 // For testing. 227 boolean isRecycled() { 228 return isRecycled; 229 } 230 231 @Override 232 public boolean isAnimated() { 233 return true; 234 } 235 236 @Override 237 public void setLoopCount(int loopCount) { 238 if (loopCount <= 0 && loopCount != LOOP_FOREVER && loopCount != LOOP_INTRINSIC) { 239 throw new IllegalArgumentException("Loop count must be greater than 0, or equal to " 240 + "GlideDrawable.LOOP_FOREVER, or equal to GlideDrawable.LOOP_INTRINSIC"); 241 } 242 243 if (loopCount == LOOP_INTRINSIC) { 244 maxLoopCount = decoder.getLoopCount(); 245 } else { 246 maxLoopCount = loopCount; 247 } 248 } 249 250 static class GifState extends ConstantState { 251 String id; 252 GifHeader gifHeader; 253 byte[] data; 254 int finalFrameWidth; 255 int finalFrameHeight; 256 Context context; 257 Transformation<Bitmap> frameTransformation; 258 int targetWidth; 259 int targetHeight; 260 GifDecoder.BitmapProvider bitmapProvider; 261 262 public GifState(String id, GifHeader header, byte[] data, Context context, 263 Transformation<Bitmap> frameTransformation, int targetWidth, int targetHeight, 264 GifDecoder.BitmapProvider provider, int finalFrameWidth, int finalFrameHeight) { 265 this.id = id; 266 gifHeader = header; 267 this.data = data; 268 this.finalFrameWidth = finalFrameWidth; 269 this.finalFrameHeight = finalFrameHeight; 270 this.context = context.getApplicationContext(); 271 this.frameTransformation = frameTransformation; 272 this.targetWidth = targetWidth; 273 this.targetHeight = targetHeight; 274 bitmapProvider = provider; 275 } 276 277 public GifState(GifState original) { 278 if (original != null) { 279 id = original.id; 280 gifHeader = original.gifHeader; 281 data = original.data; 282 context = original.context; 283 frameTransformation = original.frameTransformation; 284 targetWidth = original.targetWidth; 285 targetHeight = original.targetHeight; 286 bitmapProvider = original.bitmapProvider; 287 finalFrameWidth = original.finalFrameWidth; 288 finalFrameHeight = original.finalFrameHeight; 289 } 290 } 291 292 @Override 293 public Drawable newDrawable(Resources res) { 294 return newDrawable(); 295 } 296 297 @Override 298 public Drawable newDrawable() { 299 return new GifDrawable(this); 300 } 301 302 @Override 303 public int getChangingConfigurations() { 304 return 0; 305 } 306 } 307} 308