Tuesday, April 10, 2012

Android animation does not repeat


I'm trying to make simple animation that is repeated several times (or infinite).





It seems that android:repeatCount does not work!

Here is my animation resource from /res/anim/first_animation.xml :




<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
android:repeatCount="infinite"
>
<scale
android:interpolator="@android:anim/decelerate_interpolator"
android:duration="500"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:toXScale="1.2"
android:toYScale="1.2"
android:pivotX="50%"
android:pivotY="50%"
android:fillAfter="false" />
<scale
android:interpolator="@android:anim/accelerate_interpolator"
android:startOffset="500"
android:duration="500"
android:fromXScale="1.2"
android:fromYScale="1.2"
android:toXScale="1.0"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:fillAfter="false" />
</set>



First it should scale image from 1.0 to 1.2 size in 500 ms.

And then scale it back to 1.0 in 500 ms.

Here is how I'm using it:




Animation firstAnimation = AnimationUtils.loadAnimation(this, R.anim.first_animation);
imgView.startAnimation(firstAnimation);



It makes one cycle and then finishes.

It scales up, then scales down ans then stops.



How can I make this work as intended?


Source: Tips4all

12 comments:

  1. To expand upon answers by Pavel and others: it is true that the <set> tag is ridiculously buggy. It can't deal correctly with repeatCount and a number of other attributes.

    I spent a few hours figuring out what it can and can't deal with and have submitted a bug report/issue here: Issue 17662

    In summary (this concerns AnimationSets):


    setRepeatCount() / android:repeatCount


    This attribute (as well as repeatMode) does not work in code or XML. This makes repeating an entire set of animations difficult.


    setDuration() / android:duration


    Setting this on an AnimationSet in code WORKS (overrides all durations of children animations), but not when included in the tag in XML


    setFillAfter() / android:fillAfter


    This works in both code and XML for the tag. Strangely I have gotten it to also work without the need to set fillEnabled to true.


    setFillBefore() / android:fillBefore


    Seems to have no effect/ignored in both code and XML


    setFillEnabled() / android:fillEnabled


    Seems to have no effect/ignored in both code and XML. I can still get fillAfter to work even without including fillEnabled or setting fillEnabled to false.


    setStartOffset() / android:startOffset


    This works only in code and not XML.

    ReplyDelete
  2. I've found that <set> tag has buggy implementation in class AnimationSet.
    It can't deal correctly with repeatCount.
    What we can do - is to set repeatCount directly in <scale> tag.

    This XML resource is working well:

    <?xml version="1.0" encoding="utf-8"?>
    <scale
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:duration="200"
    android:fromXScale="1.0"
    android:fromYScale="1.0"
    android:toXScale="1.05"
    android:toYScale="1.05"
    android:pivotX="50%"
    android:pivotY="50%"
    android:repeatMode="reverse"
    android:fillAfter="false"
    android:repeatCount="24"
    />


    Unfortunately, this is limited to only one animation at once.
    We can not define a sequence of animations this way...

    ReplyDelete
  3. You should include the attribute

    android:repeatCount="infinite"


    But in your "scale" animation not in "set"

    ReplyDelete
  4. To get a repeating animation I utilized the animation listener, and called the animation again when it ended. This does a camera reticule focusing like animation with brackets.

    Here is the animation layout xml

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
    <scale
    android:fromXScale="1.0"
    android:toXScale=".7"
    android:fromYScale="1.0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toYScale=".7"
    android:duration="1000"/>
    <scale
    android:duration="1000"
    android:fromXScale=".7"
    android:toXScale="1.0"
    android:fromYScale=".7"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toYScale="1.0"
    android:startOffset="1000"/>

    </set>


    Here is the java code

    public void startAnimation() {

    View brackets = findViewById(R.id.brackets);
    brackets.setVisibility(View.VISIBLE);

    Animation anim = AnimationUtils.loadAnimation(BuzzFinderActivity.this, R.anim.crosshair_focusing);
    anim.setAnimationListener(new AnimationListener() {

    @Override
    public void onAnimationEnd(Animation arg0) {
    Animation anim = AnimationUtils.loadAnimation(BuzzFinderActivity.this, R.anim.crosshair_focusing);
    anim.setAnimationListener(this);
    brackets.startAnimation(anim);

    }

    @Override
    public void onAnimationRepeat(Animation arg0) {
    // TODO Auto-generated method stub

    }

    @Override
    public void onAnimationStart(Animation arg0) {
    // TODO Auto-generated method stub

    }

    });


    brackets.startAnimation(anim);
    }

    ReplyDelete
  5. Try to add the code to a looping thread or a while/for statement

    ReplyDelete
  6. exactly, you have to put both RepeatCount and RepeatMode to infinite

    ReplyDelete
  7. I was also facing the same problem..
    i included android:repeatCount="infinite" in XMl file..now its working fine...




    <translate
    android:fromXDelta="0"
    android:toXDelta="80"
    android:duration="1000"
    android:repeatCount="infinite"
    android:repeatMode="reverse"
    android:pivotX="50%"
    android:pivotY="50%"
    android:fillAfter="true"/>

    ReplyDelete
  8. I do most of my stuff programmatically and I may be late or inefficient on this one but this but I completed the repeat animationset goal (I a even have 2 alternating animation sets). All this code does is simply fade in one image,pause, then fade out, fade in another image, pause, fade out, and bring back the first one (rinse and repeat). I first defined my Imageviews:

    final ImageView purple = (ImageView)findViewById(R.id.purp);
    final ImageView yellow = (ImageView)findViewById(R.id.yell);
    purple.setVisibility(View.INVISIBLE);
    yellow.setVisibility(View.INVISIBLE);


    Then I made two timers, task timers and handlers to deal with when to start and stop each animation:

    Timer p = new Timer();
    TimerTask pu = new TimerTask() {
    public void run() {
    handler1.post(new Runnable() {
    public void run()
    {
    fadein(purple);
    }
    });
    }};
    p.schedule(pu, 6000, 12000);

    final Handler handler2 = new Handler();

    Timer y = new Timer();
    TimerTask ye = new TimerTask() {
    public void run() {
    handler2.post(new Runnable() {
    public void run()
    {
    fadein(yellow);
    }
    });
    }};

    y.schedule(ye, 0, 12000);


    Finally, rather than creating animation sets by adding animations, I just animations listeners to to determine when to start each animation:

    public void fadein (final ImageView image)
    {
    Animation anim = new AlphaAnimation(0, 1);

    anim.setDuration(2000);

    image.startAnimation(anim);
    anim.setAnimationListener(new AnimationListener() {
    public void onAnimationEnd(Animation animation)
    {
    image.clearAnimation();
    image.invalidate();
    pause(image);

    }

    @Override
    public void onAnimationRepeat(Animation animation) {
    // TODO Auto-generated method stub

    }

    @Override
    public void onAnimationStart(Animation animation) {
    // TODO Auto-generated method stub

    }
    });
    }
    public void pause (final ImageView image)
    {
    Animation anim = new AlphaAnimation(1, 1);

    anim.setDuration(2000);

    image.startAnimation(anim);
    anim.setAnimationListener(new AnimationListener() {
    public void onAnimationEnd(Animation animation)
    {
    image.clearAnimation();
    image.invalidate();
    fadeout(image);

    }

    @Override
    public void onAnimationRepeat(Animation animation) {
    // TODO Auto-generated method stub

    }

    @Override
    public void onAnimationStart(Animation animation) {
    // TODO Auto-generated method stub

    }
    });
    }
    public void fadeout (final ImageView image)
    {
    Animation anim = new AlphaAnimation(1,0);

    anim.setDuration(2000);

    image.startAnimation(anim);
    anim.setAnimationListener(new AnimationListener() {
    public void onAnimationEnd(Animation animation)
    {
    image.clearAnimation();
    image.invalidate();
    }

    @Override
    public void onAnimationRepeat(Animation animation) {
    // TODO Auto-generated method stub

    }

    @Override
    public void onAnimationStart(Animation animation) {
    // TODO Auto-generated method stub

    }
    });
    }


    The clearanimation and invalidate where just previous attempts and getting this thing to work right. I don't know if they are required or not.

    Hope this helps someone.



    Ryan

    ReplyDelete
  9. I've faced the same problem, but didn't want to do any timing things in Java because of the point that the UI thread may be very busy sometimes.
    The INFINITE flag doesn't work for the set tag. So I resolved the issue with a little piece of code:

    mAnimation = (AnimationSet) AnimationUtils.loadAnimation(myContext, R.anim.blink);
    mIcon.startAnimation(mAnimation);
    mAnimation.setAnimationListener(new AnimationListener() {
    public void onAnimationStart(Animation animation) {}
    public void onAnimationRepeat(Animation animation) {}
    public void onAnimationEnd(Animation animation) {
    mIcon.startAnimation(mAnimation);
    }
    });


    with the following XML:



    <alpha
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromAlpha="0.0"
    android:toAlpha="1.0" />

    <alpha
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromAlpha="0.9"
    android:startOffset="1000"
    android:toAlpha="0.0" />




    Where mIcon is an ImageView from my layout.

    ReplyDelete
  10. I've solved this problem. This is my version of the fix:

    public class HelloAndroidActivity extends Activity {
    private static String TAG = "animTest";
    private Animation scaleAnimation;
    private int currentCover = 0;
    private List<ImageView> imageViews = new ArrayList<ImageView>(3);
    private Button btn;
    private ImageView img;

    /**
    * Called when the activity is first created.
    * @param savedInstanceState If the activity is being re-initialized after
    * previously being shut down then this Bundle contains the data it most
    * recently supplied in onSaveInstanceState(Bundle). <b>Note: Otherwise it is null.</b>
    */
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.i(TAG, "onCreate");
    setContentView(R.layout.test);

    img = (ImageView)findViewById(R.id.testpict);
    imageViews.add(img);
    img = (ImageView)findViewById(R.id.testpictTwo);
    imageViews.add(img);
    img = (ImageView)findViewById(R.id.testpict3);
    imageViews.add(img);

    scaleAnimation = AnimationUtils.loadAnimation(this, R.anim.photo_scale);
    scaleAnimation.setAnimationListener(new CyclicAnimationListener());

    btn = (Button)findViewById(R.id.startBtn);
    btn.setOnClickListener(new View.OnClickListener() {

    @Override
    public void onClick(View v) {
    imageViews.get(0).startAnimation(scaleAnimation);
    }
    });



    }

    private class CyclicAnimationListener implements AnimationListener{

    @Override
    public void onAnimationEnd(Animation animation) {
    currentCover += 1;
    if(currentCover >= imageViews.size()){
    currentCover = 0;
    }
    img = imageViews.get(currentCover);
    scaleAnimation = AnimationUtils.loadAnimation(HelloAndroidActivity.this, R.anim.photo_scale);
    scaleAnimation.setAnimationListener(new CyclicAnimationListener());
    img.startAnimation(scaleAnimation);
    }

    @Override
    public void onAnimationRepeat(Animation animation) {
    Log.d("Animation", "Repeat");
    }

    @Override
    public void onAnimationStart(Animation animation) {

    }

    }

    }

    ReplyDelete
  11. i just came across this issue while working on a backwards compatible app. so frustrating! i ended up coding a nice workaround class that can be called from onCreate and will kickoff any animation resource into an indefinite loop.

    the class, AnimationLooper, is available here:
    https://gist.github.com/2018678

    ReplyDelete
  12. you can try this code.
    In your code just add,

    firstAnimation.setRepeatCount(5);


    This will repeat the animation for a definite time

    firstAnimation.setRepeatCount(Animation.INFINITE);
    firstAnimation.setRepeatMode(Animation.INFINITE);


    This will repeat the animation indefinitely.

    ReplyDelete