4
votes

I have an app with buttons that play various frame-by-frame animations when pressed. The animations are stored as sequences of .png files. Some of the animations fill a small area, and they seem to play fine using animationDrawable. However, some of the animations fill a large part of the screen (even though they are mostly transparent using an alpha channel), and I get an out of memory error when the app loads on my phone (but not the emulator). I can't play them as video files because they need to overlay the main screen.

My main question - is there a way to play large png sequence animations (Say 60 frames at 320x480) using animationDrawable?

So far in my research there doesn't seem to be any way around the memory limitations... so I have been looking into making my own method that creates an ImageView and populates it with a bitmap, and then successively replaces the bitmap with each frame after a delay. However, with this technique my animation seems to only play the first and last frames.

Here is the code of my stab at using animationDrawable - the "chimp" plays fine, but when I add the "bubbles" (the larger images), the app doesn't load at all - logCat shows: "ERROR/dalvikvm-heap(3248): 1008000-byte external allocation too large for this process... ERROR/AndroidRuntime(3248): java.lang.OutOfMemoryError: bitmap size exceeds VM budget "

public class Babymode_tst extends Activity implements OnClickListener{
 /** Called when the activity is first created. */
 AnimationDrawable chimpAnimation;
 AnimationDrawable bubblesAnimation;
 private MediaPlayer mp;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  setVolumeControlStream(AudioManager.STREAM_MUSIC);

  final ImageButton chimpButton = (ImageButton) findViewById(R.id.chimp_button);
  chimpButton.setOnClickListener(this);
  ImageView chimpImage = (ImageView) findViewById(R.id.chimp_imageview);
  chimpImage.setBackgroundResource(R.drawable.chimp_anim);
  chimpAnimation = (AnimationDrawable) chimpImage.getBackground();

  final ImageButton bubblesButton = (ImageButton) findViewById(R.id.bubbles_button);
  bubblesButton.setOnClickListener(this);
  ImageView bubblesImage = (ImageView) findViewById(R.id.bubbles_imageview);
  bubblesImage.setBackgroundResource(R.drawable.bubbles_anim);
  bubblesAnimation = (AnimationDrawable) bubblesImage.getBackground();  
 }

 public void onClick(View v) {
  if (mp != null) {
   mp.release();
  }

  Random generator = new Random();
  int randomIndex;

  switch(v.getId()){
  case R.id.chimp_button: 
    randomIndex = (generator.nextInt( 2 ) + 1);
    if (randomIndex == 1){
    mp = MediaPlayer.create(this, R.raw.chimp_1);
    }
    if (randomIndex == 2){
    mp = MediaPlayer.create(this, R.raw.chimp_2);
   }
   mp.start();

   chimpAnimation.setVisible(true,true);
   chimpAnimation.start();

   break;

  case R.id.bubbles_button: 
    randomIndex = (generator.nextInt( 3 ) + 1);
    if (randomIndex == 1){
    mp = MediaPlayer.create(this, R.raw.bubbles_1);
    }
    if (randomIndex == 2){
     mp = MediaPlayer.create(this, R.raw.bubbles_2);
   }
   if (randomIndex == 3){
     mp = MediaPlayer.create(this, R.raw.bubbles_3);
   }
   mp.start();

   bubblesAnimation.setVisible(true,true);
   bubblesAnimation.start();
   break;
  }
 }
}

And here is my attempt at playing the frames one by one in an ImageView, but it only plays the first and last frames:

    public void playAnimation(String name){
  final ImageView bubblesImageView = (ImageView) findViewById(R.id.bubbles_imageview);
  bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0003);

  mHandler.postDelayed(new Runnable() {
   public void run() {
    bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0015);
   }
  }, 500);

  mHandler.postDelayed(new Runnable() {
   public void run() {
    bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0025);
   }
  }, 500);

  mHandler.postDelayed(new Runnable() {
   public void run() {
    bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0035);
   }
  }, 500);


 }

Any help would be appreciated - thanks

3

3 Answers

1
votes

your mHandler.postDelayed(new Runnable(){...}); are all triggering at the same time e.g. after 500 ms, you need them to trigger one after another by either

nHandler.postDelayed(new Runnable() {
   public void run() {
    bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0015);
   }
  }, 500);

  mHandler.postDelayed(new Runnable() {
   public void run() {
    bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0025);
   }
  }, 1000);

  mHandler.postDelayed(new Runnable() {
   public void run() {
    bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0035);
   }
  }, 1500);

or having each runnable queue up the next one after a 500 ms delay e.g.

mHandler.postDelayed(new Runnable() {
public void run() {
   bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0015);

   mHandler.postDelayed(new Runnable() {
   public void run() {
      bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0025);
      }
   }, 1000); 
   }
}, 500);

Or you could construct an array of ìnt with the R.drawable.xxx value and every 500 ms just display the next image.

1
votes

This is an extremely difficult thing to do, but it is doable. The key is to recycle your bitmaps right after you use them. Check out this wrapper class someone wrote that will handle all that for you: http://snippetrepo.com/snippets/android-animation-without-outofmemoryerror

0
votes

In my company we got the same problem, we've tried to make a splash screen in this way(and it was total disaster). If you want so hard to got some kind of animation, generate video from the PNG frames (i think that FFMPEG can do it), and then play the video.