Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Android animation-list alternative that avoids OutOfMemory
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
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);
int imageResId = context.getResources().getIdentifier(resourceName, "drawable", context.getApplicationInfo().packageName);
return imageResId;
}
@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
You can’t perform that action at this time.