I am trying to show frame by frame animation by changing images in a imageview. I tried animation drawable in xml and also changing the bitmap of the imageview inside a Handler. I also tried to store only three bitmaps inside a arraylist(to avoid out of memory) as a caching mechanism but really low improvement. I need to iterate 36 images for a full animation. The problem i am facing is that in all the methods I used I cannot complete the animation in the given timeframe of 50ms. The images range from 250 kb smallest to 540 kb maximum. The fps of animation is really low. As the ios version of the app is ready I am constrained to show animation consistent to the ios version. I am a noob in renderscript and opengl. Is there any way to show a smooth animation for large images in 50-60ms. Any hints or suggestions is highly appreciated. Heres a snapshot of the animation: Here's the link to my images for any one intrested.
It's Time-Consuming:Having to produce each frame one-by-one is an incredibly time-consuming process. This is especially true for traditional animation, which requires an extremely detailed drawing to be completed for every new frame.
Most modern CGI or Flash, Harmony, or puppet animation is done on 30 frames per second. There are a few that are done on 24, but I think the majority is on 30. But traditionally, animators worked on 24 frames per second.
It's commonly accepted that 60 frames per second is the rate at which animations will appear smooth.
To create a frame-by-frame animation, define each frame as a keyframe and create a different image for each frame. Each new keyframe initially contains the same contents as the keyframe preceding it, so you can modify the frames in the animation incrementally.
I wrote a simple activity that does the most basic thing:
Loads all bitmaps in a Thread, then posts a change to an ImageView every 40ms.
package mk.testanimation;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.ImageView;
import java.util.ArrayList;
public class MainActivity extends Activity {
ImageView mImageView;
private int mImageRes[] = new int[]{
R.drawable.s0,
R.drawable.s1,
R.drawable.s2,
R.drawable.s3,
R.drawable.s4,
R.drawable.s5,
R.drawable.s6,
R.drawable.s7,
R.drawable.s8,
R.drawable.s9,
R.drawable.s10,
R.drawable.s11,
R.drawable.s12,
R.drawable.s13,
R.drawable.s14,
R.drawable.s15,
R.drawable.s16,
R.drawable.s17,
R.drawable.s18,
R.drawable.s19,
R.drawable.s20,
R.drawable.s21,
R.drawable.s22,
R.drawable.s23,
R.drawable.s24,
R.drawable.s25,
R.drawable.s26,
R.drawable.s27,
R.drawable.s28,
R.drawable.s29,
R.drawable.s30,
R.drawable.s31,
R.drawable.s32,
R.drawable.s33,
R.drawable.s34,
R.drawable.s35,
};
private ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(mImageRes.length);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Handler handler = new Handler();
mImageView = new ImageView(this);
setContentView(mImageView);
Thread important = new Thread() {
@Override
public void run() {
long timestamp = System.currentTimeMillis();
for (int i = 0; i < mImageRes.length; i++) {
mBitmaps.add(BitmapFactory.decodeResource(getResources(), mImageRes[i]));
}
Log.d("ANIM-TAG", "Loading all bitmaps took " + (System.currentTimeMillis() - timestamp) + "ms");
for (int i = 0; i < mBitmaps.size(); i++) {
final int idx = i;
handler.postDelayed(new Runnable() {
@Override
public void run() {
mImageView.setImageBitmap(mBitmaps.get(idx));
}
}, i * 40);
}
}
};
important.setPriority(Thread.MAX_PRIORITY);
important.start();
}
}
This looked pretty decent on my Nexus 7, but it did take a little over 4s to load all the bitmaps.
Can you load the bitmaps in advance?
Also, it won't save a ton, but your pngs have a bunch of padding around the transparent space. You can crop them and reduce the memory a bit. Otherwise compressing the images will also help (like limiting the number of colors used).
Ideally, in the above solution, you'd recycle the bitmaps immediately after they're no longer used.
Also, if that's too memory-heavy, you could do as you mentioned, and have a Bitmap buffer, but I'm pretty sure it'll need to be more than 3 images large.
Good luck.
EDIT: Attempt 2. First, I cropped all the images to 590x590. This shaved about 1mb off the images. Then I created a new class, which is a bit "busy" and doesn't have a fixed frame rate but renders the images as soon as they are ready:
package mk.testanimation;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.ImageView;
import java.util.ArrayList;
public class MainActivity extends Activity {
ImageView mImageView;
private int mImageRes[] = new int[]{R.drawable.s0, R.drawable.s1, R.drawable.s2, R.drawable.s3, R.drawable.s4, R.drawable.s5, R.drawable.s6, R.drawable.s7, R.drawable.s8, R.drawable.s9, R.drawable.s10, R.drawable.s11, R.drawable.s12, R.drawable.s13, R.drawable.s14, R.drawable.s15, R.drawable.s16, R.drawable.s17, R.drawable.s18, R.drawable.s19, R.drawable.s20, R.drawable.s21, R.drawable.s22, R.drawable.s23, R.drawable.s24, R.drawable.s25, R.drawable.s26, R.drawable.s27, R.drawable.s28, R.drawable.s29, R.drawable.s30, R.drawable.s31, R.drawable.s32, R.drawable.s33, R.drawable.s34, R.drawable.s35};
private ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(mImageRes.length);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final long timestamp = System.currentTimeMillis();
final Handler handler = new Handler();
mImageView = new ImageView(this);
setContentView(mImageView);
Thread important = new Thread() {
@Override
public void run() {
for (int i = 0; i < mImageRes.length; i++) {
mBitmaps.add(BitmapFactory.decodeResource(getResources(), mImageRes[i]));
}
}
};
important.setPriority(Thread.MAX_PRIORITY);
important.start();
Thread drawing = new Thread() {
@Override
public void run() {
int i = 0;
while (i < mImageRes.length) {
if (i >= mBitmaps.size()) {
Thread.yield();
} else {
final Bitmap bitmap = mBitmaps.get(i);
handler.post(new Runnable() {
@Override
public void run() {
mImageView.setImageBitmap(bitmap);
}
});
i++;
}
}
Log.d("ANIM-TAG", "Time to render all frames:" + (System.currentTimeMillis() - timestamp) + "ms");
}
};
drawing.setPriority(Thread.MAX_PRIORITY);
drawing.start();
}
}
The above got the rendering to start nearly immediately, and it took less than 4s on my 2012 Nexus 7.
As a last effort, I converted all the images to 8-bit PNGs instead of 32-bit. This brought the rendering to under 2 seconds!
I'll bet any solution you end up with will benefit from making the images as small as possible.
Again -- Good Luck!
I'm facing the same problem and I have solved it by overriding an AnimationDrawable. So, if the problem is you that can't load all images in array because it is too big for the memory to hold it, then load the image when you need it. My AnimationDrawable is this:
public abstract class MyAnimationDrawable extends AnimationDrawable {
private Context context;
private int current;
private int reqWidth;
private int reqHeight;
private int totalTime;
public MyAnimationDrawable(Context context, int reqWidth, int reqHeight) {
this.context = context;
this.current = 0;
//In my case size of screen to scale Drawable
this.reqWidth = reqWidth;
this.reqHeight = reqHeight;
this.totalTime = 0;
}
@Override
public void addFrame(Drawable frame, int duration) {
super.addFrame(frame, duration);
totalTime += duration;
}
@Override
public void start() {
super.start();
new Handler().postDelayed(new Runnable() {
public void run() {
onAnimationFinish();
}
}, totalTime);
}
public int getTotalTime() {
return totalTime;
}
@Override
public void draw(Canvas canvas) {
try {
//Loading image from assets, you could make it from resources
Bitmap bmp = BitmapFactory.decodeStream(context.getAssets().open("presentation/intro_000"+(current < 10 ? "0"+current : current)+".jpg"));
//Scaling image to fitCenter
Matrix m = new Matrix();
m.setRectToRect(new RectF(0, 0, bmp.getWidth(), bmp.getHeight()), new RectF(0, 0, reqWidth, reqHeight), Matrix.ScaleToFit.CENTER);
bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
//Calculating the start 'x' and 'y' to paint the Bitmap
int x = (reqWidth - bmp.getWidth()) / 2;
int y = (reqHeight - bmp.getHeight()) / 2;
//Painting Bitmap in canvas
canvas.drawBitmap(bmp, x, y, null);
//Jump to next item
current++;
} catch (IOException e) {
e.printStackTrace();
}
}
abstract void onAnimationFinish();
}
Now to play animation you need to do what next
//Get your ImageView
View image = MainActivity.this.findViewById(R.id.presentation);
//Create AnimationDrawable
final AnimationDrawable animation = new MyAnimationDrawable(this, displayMetrics.widthPixels, displayMetrics.heightPixels) {
@Override
void onAnimationFinish() {
//Do something when finish animation
}
};
animation.setOneShot(true); //dont repeat animation
//This is just to say that my AnimationDrawable has 72 frames with 50 milliseconds interval
try {
//Always load same bitmap, anyway you load the right one in draw() method in MyAnimationDrawable
Bitmap bmp = BitmapFactory.decodeStream(MainActivity.this.getAssets().open("presentation/intro_00000.jpg"));
for (int i = 0; i < 72; i++) {
animation.addFrame(new BitmapDrawable(getResources(), bmp), 50);
}
} catch (IOException e) {
e.printStackTrace();
}
//Set AnimationDrawable to ImageView
if (Build.VERSION.SDK_INT < 16) {
image.setBackgroundDrawable(animation);
} else {
image.setBackground(animation);
}
//Start animation
image.post(new Runnable() {
@Override
public void run() {
animation.start();
}
});
That is all, and works OK form me!!!
Try using an AnimationDrawable
for your ImageView
. See my answer here for example.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With