Created
February 2, 2015 18:29
-
-
Save miensol/f4d6aa65fb09d627ae7e to your computer and use it in GitHub Desktop.
Android animation-list alternative that avoids OutOfMemory
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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