3

I am trying to animate a set of images using AnimationDrawable. The user should have the ability to pause/resume the animation on a click of a button. I am doing so, using setVisible(boolean visible, boolean restart). Not completely sure if I understand the documentation correctly, but every time pause is pressed, the animation starts from the beginning.

Is there any way to resume the animation from the same frame?

From AnimationDrawable API:

...If restart is false, the drawable will resume from the most recent frame

Just to be clear:

activityAnimation.setVisible(false, false); Should stop the animation.

activityAnimation.setVisible(true, false); Should resume the animation from the same frame (it doesn't).


Here is my code:

The animation start() is called in onWindowFocusChanged.

The animation and the buttons are created in onCreate:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_content);

    ImageView activityImage = (ImageView) findViewById(R.id.activity_image);
    activityImage.setBackgroundResource(R.drawable.anim);
    activityAnimation = (AnimationDrawable) activityImage.getBackground();

    ....
    b.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            ImageButton b = (ImageButton) v;
            if ((Integer)b.getTag() == R.drawable.pause) {
                b.setImageResource(R.drawable.play);
                b.setTag(R.drawable.play);
                activityAnimation.setVisible(false, false);
            } else {
                b.setImageResource(R.drawable.pause);
                b.setTag(R.drawable.pause);
                activityAnimation.setVisible(true, false);
            }
        }
    });...
}

This is the content of the XML file

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">

    <item android:drawable="@drawable/one" android:duration="1000" />
    <item android:drawable="@drawable/two" android:duration="1000" />
    <item android:drawable="@drawable/three" android:duration="1000" />
    <item android:drawable="@drawable/four" android:duration="1000" />

</animation-list>

I have noticed several of these old posts, but non had the answer.

How to reset AnimationDrawable (The other side of this issue)

How do I pause frame animation using AnimationDrawable? [Closed]

Community
  • 1
  • 1
Idan
  • 5,405
  • 7
  • 35
  • 52
  • On what version of Android are you seeing this behavior? – user Jan 31 '17 at 08:15
  • To simulate I'm using Genymotion (Galaxy s5 - 4.4.4 - API19). Android studio 2.2.3, latest SDK – Idan Jan 31 '17 at 08:19
  • Try a higher API level device(Lollipop and above) and see if the behavior persists. I've looked at the code of AnimationDrawable and it seems that on lower versions it simply restarts the animation, while higher versions do have some logic to animate from the current frame. – user Jan 31 '17 at 08:23
  • Check on API 23, same issue. – Idan Jan 31 '17 at 08:55
  • I've seen it myself. As I said on lower versions the code simply resets the animation while on higher versions it tries to resume it (but apparently fails). So, its either a bug in the code or the documentation is misleading. As an alternative you could replicate the animation by loading the animation drawables in an array and then using a Handler to post Runnables updating the ImageView's background with a drawable from the above array(which is what, basically, an AnimationDrawable is doing). – user Jan 31 '17 at 10:08

2 Answers2

1

To pause and resume AnimationDrawable, simply customize it and handle with only one variable.

class CustomAnimationDrawable1() : AnimationDrawable() {

private var finished = false
private var animationFinishListener: AnimationFinishListener? = null
private var isPause = false
private var currentFrame = 0

fun setFinishListener(animationFinishListener: AnimationFinishListener?) {
    this.animationFinishListener = animationFinishListener
}

interface AnimationFinishListener {
    fun onAnimationFinished()
}


override fun selectDrawable(index: Int): Boolean {
    val ret = if (isPause) {
        super.selectDrawable(currentFrame)
    } else {
        super.selectDrawable(index)
    }

    if (!isPause) {
        currentFrame = index
        if (index == numberOfFrames - 1) {
            if (!finished && ret && !isOneShot) {
                finished = true
                if (animationFinishListener != null) animationFinishListener!!.onAnimationFinished()
            }
        }
    }
    return ret
}

fun pause() {
    isPause = true
}

fun resume() {
    isPause = false
}

}

kinjal patel
  • 585
  • 2
  • 4
  • 12
0

Since I needed a timer on the screen as well, the answer in my case was simply to create a runnable replace the ImageView background every second.

Here is the general idea:

  1. Create the runnable, ImageView and few helpers:

    ImageView exImage = null;
    
    long startTime = 0;
    long pauseTime = 0;
    long pauseStartTime = 0;
    
    List<Integer> animList = new ArrayList<>();
    int animIt = 0;        
    
    Handler timerHandler = new Handler();
    Runnable timerRunnable = new Runnable() {
        @Override
        public void run() {
    
            long millis = System.currentTimeMillis() - startTime - pauseTime;
            double dSecs = (double) (millis / 100);
            int pace = 10;
    
            if (dSecs % pace == 0.0 && !animList.isEmpty()) {
                animIt = (animIt == animList.size() - 1) ? 0 : animIt + 1;
                exImage.setBackgroundResource(animList.get(animIt));
            }
            timerHandler.postDelayed(this, 100);
        }
    };
    
  2. In OnCreate bind the pause and play buttons, fire the timer and set the first ImageView

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_exercise);
    
        // Set the image view
        exImage = (ImageView) findViewById(R.id.image_zero);
        exImage.setBackgroundResource(R.drawable.fe_0);
    
        // Start the timer
        timerHandler.postDelayed(timerRunnable, 0);
    
        // Bind the buttons
        ImageButton pp = (ImageButton) findViewById(R.id.button_play_pause_toggle);
        pp.setImageResource(R.drawable.pause);
        pp.setTag(R.drawable.pause);
    
        pp.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ImageButton pp = (ImageButton) v;
                //Pause
                if ((Integer) pp.getTag() == R.drawable.pause) {
                    pauseStartTime = System.currentTimeMillis();
                    timerHandler.removeCallbacks(timerRunnable);
                } else { // Resume
                    pauseTime += System.currentTimeMillis() - pauseStartTime;
                    timerHandler.postDelayed(timerRunnable, 0);
                }
            }
        });
    }
    
Idan
  • 5,405
  • 7
  • 35
  • 52