package com.felipecsl.gifimageview.library; import android.content.Context; import android.graphics.Bitmap; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.util.Log; import android.widget.ImageView; public class GifImageView extends ImageView implements Runnable { private static final String TAG = "GifDecoderView"; private GifDecoder gifDecoder; private Bitmap tmpBitmap; private final Handler handler = new Handler(Looper.getMainLooper()); private boolean animating; private boolean renderFrame; private boolean shouldClear; private Thread animationThread; private OnFrameAvailable frameCallback = null; private long framesDisplayDuration = -1L; private OnAnimationStop animationStopCallback = null; private OnAnimationStart animationStartCallback = null; private final Runnable updateResults = new Runnable() { @Override public void run() { if (tmpBitmap != null && !tmpBitmap.isRecycled()) { setImageBitmap(tmpBitmap); } } }; private final Runnable cleanupRunnable = new Runnable() { @Override public void run() { tmpBitmap = null; gifDecoder = null; animationThread = null; shouldClear = false; } }; public GifImageView(final Context context, final AttributeSet attrs) { super(context, attrs); } public GifImageView(final Context context) { super(context); } public void setBytes(final byte[] bytes) { gifDecoder = new GifDecoder(); try { gifDecoder.read(bytes); } catch (final Exception e) { gifDecoder = null; Log.e(TAG, e.getMessage(), e); return; } if(animating){ startAnimationThread(); } else { gotoFrame(0); } } public long getFramesDisplayDuration() { return framesDisplayDuration; } /** * Sets custom display duration in milliseconds for the all frames. Should be called before {@link * #startAnimation()} * * @param framesDisplayDuration Duration in milliseconds. Default value = -1, this property will * be ignored and default delay from gif file will be used. */ public void setFramesDisplayDuration(long framesDisplayDuration) { this.framesDisplayDuration = framesDisplayDuration; } public void startAnimation() { animating = true; startAnimationThread(); } public boolean isAnimating() { return animating; } public void stopAnimation() { animating = false; if (animationThread != null) { animationThread.interrupt(); animationThread = null; } } public void gotoFrame(int frame){ if(gifDecoder.getCurrentFrameIndex() == frame) return; if(gifDecoder.setFrameIndex(frame-1) && !animating){ renderFrame = true; startAnimationThread(); } } public void resetAnimation(){ gifDecoder.resetLoopIndex(); gotoFrame(0); } public void clear() { animating = false; renderFrame = false; shouldClear = true; stopAnimation(); handler.post(cleanupRunnable); } private boolean canStart() { return (animating || renderFrame) && gifDecoder != null && animationThread == null; } public int getGifWidth() { return gifDecoder.getWidth(); } public int getGifHeight() { return gifDecoder.getHeight(); } @Override public void run() { if (animationStartCallback != null) { animationStartCallback.onAnimationStart(); } do { if (!animating && !renderFrame) { break; } boolean advance = gifDecoder.advance(); //milliseconds spent on frame decode long frameDecodeTime = 0; try { long before = System.nanoTime(); tmpBitmap = gifDecoder.getNextFrame(); if (frameCallback != null) { tmpBitmap = frameCallback.onFrameAvailable(tmpBitmap); } frameDecodeTime = (System.nanoTime() - before) / 1000000; handler.post(updateResults); } catch (final ArrayIndexOutOfBoundsException | IllegalArgumentException e) { Log.w(TAG, e); } renderFrame = false; if (!animating || !advance) { animating = false; break; } try { int delay = gifDecoder.getNextDelay(); // Sleep for frame duration minus time already spent on frame decode // Actually we need next frame decode duration here, // but I use previous frame time to make code more readable delay -= frameDecodeTime; if (delay > 0) { Thread.sleep(framesDisplayDuration > 0 ? framesDisplayDuration : delay); } } catch (final InterruptedException e) { // suppress exception } } while (animating); if (shouldClear) { handler.post(cleanupRunnable); } animationThread = null; if (animationStopCallback != null) { animationStopCallback.onAnimationStop(); } } public OnFrameAvailable getOnFrameAvailable() { return frameCallback; } public void setOnFrameAvailable(OnFrameAvailable frameProcessor) { this.frameCallback = frameProcessor; } public interface OnFrameAvailable { Bitmap onFrameAvailable(Bitmap bitmap); } public OnAnimationStop getOnAnimationStop() { return animationStopCallback; } public void setOnAnimationStop(OnAnimationStop animationStop) { this.animationStopCallback = animationStop; } public void setOnAnimationStart(OnAnimationStart animationStart) { this.animationStartCallback = animationStart; } public interface OnAnimationStop { void onAnimationStop(); } public interface OnAnimationStart { void onAnimationStart(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); clear(); } private void startAnimationThread() { if (canStart()) { animationThread = new Thread(this); animationThread.start(); } } }