Created
July 8, 2016 18:24
-
-
Save FlaviusPopescu/3d2f1b5a6b95244102666df65fd5000a to your computer and use it in GitHub Desktop.
This file contains 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
/** | |
* 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