Skip to content

Instantly share code, notes, and snippets.

@FlaviusPopescu
Created July 8, 2016 18:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FlaviusPopescu/3d2f1b5a6b95244102666df65fd5000a to your computer and use it in GitHub Desktop.
Save FlaviusPopescu/3d2f1b5a6b95244102666df65fd5000a to your computer and use it in GitHub Desktop.
/**
* A helper view class useful in playing animation in a more memory efficient way, since Bitmaps
* are preloaded on a background handlerThread and reused, while the main thread "plays" the frames.
* To specify which animation frames should be played you need to provide an implementation of
* FramesDataSource.
*/
public class FrameAnimationView extends FrameLayout {
private static final String TAG = FrameAnimationView.class.getSimpleName();
private final ImageView imageView;
private final Handler animationHandler;
private final LoadNextFrameRunnable nextFrameRunnable;
private FramesDataSource framesDataSource;
private boolean isRunning;
private int nextFrame;
private BitmapFactory.Options reuseBitmapOptions;
public FrameAnimationView(Context context, AttributeSet attrs) {
super(context, attrs);
addView(imageView = new ImageView(context), new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
animationHandler = new Handler();
nextFrameRunnable = new LoadNextFrameRunnable();
}
public void setFramesDataSource(FramesDataSource framesDataSource) {
this.framesDataSource = framesDataSource;
}
public void start() {
if (isRunning) {
return;
}
nextFrame = 0;
isRunning = true;
animationHandler.post(nextFrameRunnable);
}
@Override
protected void onDetachedFromWindow() {
stop();
super.onDetachedFromWindow();
}
public void stop() {
if (!isRunning) {
return;
}
animationHandler.removeCallbacks(nextFrameRunnable);
isRunning = false;
}
private long getFrameDurationInMillis(int frame) {
return framesDataSource.getFrameDurationInMillis(getContext(), frame);
}
private BitmapFactory.Options getReusableBitmapOptions(Bitmap bitmap) {
if (reuseBitmapOptions == null || reuseBitmapOptions.inBitmap != bitmap) {
reuseBitmapOptions = new BitmapFactory.Options();
reuseBitmapOptions.inBitmap = bitmap;
reuseBitmapOptions.inMutable = true;
reuseBitmapOptions.inSampleSize = 1;
}
return reuseBitmapOptions;
}
private int moveToNextFrame() {
int frameResourceId = framesDataSource.getFrameResourceId(getContext(), nextFrame);
nextFrame = (nextFrame + 1) % framesDataSource.getFramesCount();
return frameResourceId;
}
public interface FramesDataSource {
int getFramesCount();
int getFrameResourceId(Context context, int frameIndex);
int getFrameDurationInMillis(Context context, int frame);
}
public static class FormatFramesDataSource implements FramesDataSource {
private final String resourceNameFormat;
private final int totalFrames;
public FormatFramesDataSource(String resourceNameFormat, int totalFrames) {
this.resourceNameFormat = resourceNameFormat;
this.totalFrames = totalFrames;
}
@Override
public int getFramesCount() {
return totalFrames;
}
@Override
public int getFrameResourceId(Context context, int frameIndex) {
String resourceName = String.format(resourceNameFormat, frameIndex);
return context.getResources().getIdentifier(resourceName, "drawable", context.getApplicationInfo().packageName);
}
@Override
public int getFrameDurationInMillis(Context context, int frame) {
return 200;
}
}
private class LoadNextFrameRunnable implements Runnable {
@Override
public void run() {
long startMillis = System.currentTimeMillis();
int frameIndex = nextFrame;
long frameDurationInMillis = getFrameDurationInMillis(frameIndex);
int frameResId = moveToNextFrame();
loadFrame(frameIndex, frameResId);
long tookMillis = System.currentTimeMillis() - startMillis;
Log.v(TAG, "Took " + tookMillis + "ms to load frameIndex " + frameIndex);
animationHandler.postDelayed(this, Math.max(frameDurationInMillis - tookMillis, 0));
}
private void loadFrame(int frameIndex, int frameResId) {
Drawable drawable = imageView.getDrawable();
Bitmap bitmap = null;
if (drawable != null && drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
try {
bitmap = BitmapFactory.decodeResource(getResources(), frameResId, getReusableBitmapOptions(bitmapDrawable.getBitmap()));
} catch (Exception e) {
Log.e(TAG, "Failed to load bitmap for " + frameResId + " (frameIndex " + frameIndex + ")", e);
}
}
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(frameResId);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment