How to make a smooth image rotation in Android?
Asked Answered
P

19

254

I'm using a RotateAnimation to rotate an image that I'm using as a custom cyclical spinner in Android. Here's my rotate_indefinitely.xml file, which I placed in res/anim/:

<?xml version="1.0" encoding="UTF-8"?>
<rotate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:toDegrees="360"
    android:pivotX="50%"
    android:pivotY="50%"
    android:repeatCount="infinite"
    android:duration="1200" />    

When I apply this to my ImageView using AndroidUtils.loadAnimation(), it works great!

spinner.startAnimation( 
    AnimationUtils.loadAnimation(activity, R.anim.rotate_indefinitely) );

The one problem is that the image rotation seems to pause at the top of every cycle.

In other words, the image rotates 360 degrees, pauses briefly, then rotates 360 degrees again, etc.

I suspect that the problem is that the animation is using a default interpolator like android:iterpolator="@android:anim/accelerate_interpolator" (AccelerateInterpolator), but I don't know how to tell it not to interpolate the animation.

How can I turn off interpolation (if that is indeed the problem) to make my animation cycle smoothly?

Pozzy answered 27/10, 2009 at 23:20 Comment(0)
E
215

You are right about AccelerateInterpolator; you should use LinearInterpolator instead.

You can use the built-in android.R.anim.linear_interpolator from your animation XML file with android:interpolator="@android:anim/linear_interpolator".

Or you can create your own XML interpolation file in your project, e.g. name it res/anim/linear_interpolator.xml:

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

And add to your animation XML:

android:interpolator="@anim/linear_interpolator"

Special Note: If your rotate animation is inside a set, setting the interpolator does not seem to work. Making the rotate the top element fixes it. (this will save your time.)

Elater answered 28/10, 2009 at 11:13 Comment(4)
This is because interpolator was spelled wrong (no "n"). You don't need to make your ownBower
I've tried every interpolator available, including the linear, and i still get this little "hitch" at the beginning of every cycle.Lilylilyan
If your rotate animation is inside a set, setting the interpolator does not seem to work. Making the rotate the top element fixes itFrankiefrankincense
Hey, what if you actually want to use accelerate_decelerate_interpolator without the small "pause" between every animations ?Colwell
R
123

I had this problem as well, and tried to set the linear interpolator in xml without success. The solution that worked for me was to create the animation as a RotateAnimation in code.

RotateAnimation rotate = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rotate.setDuration(5000);
rotate.setInterpolator(new LinearInterpolator());

ImageView image= (ImageView) findViewById(R.id.imageView);

image.startAnimation(rotate);
Rumilly answered 7/5, 2015 at 13:9 Comment(3)
if you want the animation to stay at the orientation at the end, add rotate.setFillAfter(true);Varve
if you want the animation to stay permanently with no end, addrotate.setRepeatCount(Animation.INFINITE);Triarchy
if you want the object to rotate infinitely, you need to set the degree from 180 to 360. also, setFillAfter as mention by Fonix is neededStonedeaf
G
51

This works fine

<?xml version="1.0" encoding="UTF-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1600"
    android:fromDegrees="0"
    android:interpolator="@android:anim/linear_interpolator"
    android:pivotX="50%"
    android:pivotY="50%"
    android:repeatCount="infinite"
    android:toDegrees="358" />

To reverse rotate:

<?xml version="1.0" encoding="UTF-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1600"
    android:fromDegrees="358"
    android:interpolator="@android:anim/linear_interpolator"
    android:pivotX="50%"
    android:pivotY="50%"
    android:repeatCount="infinite"
    android:toDegrees="0" />
Guanidine answered 26/4, 2013 at 12:5 Comment(3)
For reverse, just have repeatcount 2 and set android:repeatMode="reverse" - no need to have two different XML files.Keller
why 358 and not 359 or 360?Cockup
Where to add this file to in resources? Is animation a separate package for this?Saphead
D
43

Maybe, something like this will help:

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        imageView.animate().rotationBy(360).withEndAction(this).setDuration(3000).setInterpolator(new LinearInterpolator()).start();
    }
};

imageView.animate().rotationBy(360).withEndAction(runnable).setDuration(3000).setInterpolator(new LinearInterpolator()).start();

By the way, you can rotate by more than 360 like:

imageView.animate().rotationBy(10000)...
Dairen answered 7/10, 2015 at 20:12 Comment(4)
Working perfect, is imageView.clearAnimation() will clean up the runnable as well ?Lilian
Its working fine for start animation, but after start runnable its not clean up by imageView.clearAnimation(), i used in touch listener eventHelbonna
Can't stop these runnables independentlyConversazione
@Conversazione you can call withEndAction(this) on some condition. If the condition is false, withEndAction(this) won't be called, and animation should stopDairen
M
32

Try using toDegrees="359" since 360° and 0° are the same.

Megaron answered 17/3, 2011 at 18:6 Comment(0)
H
26
ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).setDuration(300).start();

Try this.

Hole answered 24/8, 2017 at 14:41 Comment(0)
C
17

Rotation Object programmatically.

// clockwise rotation :

    public void rotate_Clockwise(View view) {
        ObjectAnimator rotate = ObjectAnimator.ofFloat(view, "rotation", 180f, 0f);
//        rotate.setRepeatCount(10);
        rotate.setDuration(500);
        rotate.start();
    }

// AntiClockwise rotation :

 public void rotate_AntiClockwise(View view) {
        ObjectAnimator rotate = ObjectAnimator.ofFloat(view, "rotation", 0f, 180f);
//        rotate.setRepeatCount(10);
        rotate.setDuration(500);
        rotate.start();
    } 

view is object of your ImageView or other widgets.

rotate.setRepeatCount(10); use to repeat your rotation.

500 is your animation time duration.

Cornerwise answered 25/10, 2018 at 7:13 Comment(0)
M
12

Pruning the <set>-Element that wrapped the <rotate>-Element solves the problem!

Thanks to Shalafi!

So your Rotation_ccw.xml should loook like this:

<?xml version="1.0" encoding="utf-8"?>

<rotate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:toDegrees="-360"
    android:pivotX="50%"
    android:pivotY="50%"
    android:duration="2000"
    android:fillAfter="false"
    android:startOffset="0"
    android:repeatCount="infinite"
    android:interpolator="@android:anim/linear_interpolator"
    />
Mathi answered 26/6, 2013 at 14:58 Comment(1)
This is actually the correct answer, replacing the default interpolator!Livingston
A
6

In Kotlin:

 ivBall.setOnClickListener(View.OnClickListener {

            //Animate using XML
            // val rotateAnimation = AnimationUtils.loadAnimation(activity, R.anim.rotate_indefinitely)

            //OR using Code
            val rotateAnimation = RotateAnimation(
                    0f, 359f,
                    Animation.RELATIVE_TO_SELF, 0.5f,
                    Animation.RELATIVE_TO_SELF, 0.5f

            )
            rotateAnimation.duration = 300
            rotateAnimation.repeatCount = 2

            //Either way you can add Listener like this
            rotateAnimation.setAnimationListener(object : Animation.AnimationListener {

                override fun onAnimationStart(animation: Animation?) {
                }

                override fun onAnimationRepeat(animation: Animation?) {
                }

                override fun onAnimationEnd(animation: Animation?) {

                    val rand = Random()
                    val ballHit = rand.nextInt(50) + 1
                    Toast.makeText(context, "ballHit : " + ballHit, Toast.LENGTH_SHORT).show()
                }
            })

            ivBall.startAnimation(rotateAnimation)
        })
Antonomasia answered 14/7, 2018 at 16:16 Comment(0)
I
5

As hanry has mentioned above putting liner iterpolator is fine. But if rotation is inside a set you must put android:shareInterpolator="false" to make it smooth.

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
**android:shareInterpolator="false"**
>
<rotate
    android:interpolator="@android:anim/linear_interpolator"
    android:duration="300"
    android:fillAfter="true"
    android:repeatCount="10"
    android:repeatMode="restart"
    android:fromDegrees="0"
    android:toDegrees="360"
    android:pivotX="50%"
    android:pivotY="50%" />
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/linear_interpolator"
    android:duration="3000"
    android:fillAfter="true"
    android:pivotX="50%"
    android:pivotY="50%"
    android:fromXScale="1.0"
    android:fromYScale="1.0"
    android:toXScale="0"
    android:toYScale="0" />
</set>

If Sharedinterpolator being not false, the above code gives glitches.

Iselaisenberg answered 13/1, 2015 at 20:9 Comment(0)
A
4

If you are using a set Animation like me you should add the interpolation inside the set tag:

<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator">

 <rotate
    android:duration="5000"
    android:fromDegrees="0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:repeatCount="infinite"
    android:startOffset="0"
    android:toDegrees="360" />

 <alpha
    android:duration="200"
    android:fromAlpha="0.7"
    android:repeatCount="infinite"
    android:repeatMode="reverse"
    android:toAlpha="1.0" />

</set>

That Worked for me.

Arak answered 26/5, 2015 at 18:30 Comment(1)
comes alive with alphaAbreaction
P
4

Here is code snippet that worked fine for me:

    RotateAnimation rotate = new RotateAnimation(
            0, 359,
            Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f
    );

    rotate.setDuration(1500);
    rotate.setRepeatCount(Animation.INFINITE);
    yourView.startAnimation(rotate);

Check it's not 360 it's 359 in the constructor as 0 and 360 are at the same point.

Purloin answered 27/7, 2021 at 13:40 Comment(0)
D
3

No matter what I tried, I couldn't get this to work right using code (and setRotation) for smooth rotation animation. What I ended up doing was making the degree changes so small, that the small pauses are unnoticeable. If you don't need to do too many rotations, the time to execute this loop is negligible. The effect is a smooth rotation:

        float lastDegree = 0.0f;
        float increment = 4.0f;
        long moveDuration = 10;
        for(int a = 0; a < 150; a++)
        {
            rAnim = new RotateAnimation(lastDegree, (increment * (float)a),  Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            rAnim.setDuration(moveDuration);
            rAnim.setStartOffset(moveDuration * a);
            lastDegree = (increment * (float)a);
            ((AnimationSet) animation).addAnimation(rAnim);
        }
Debidebilitate answered 25/8, 2012 at 19:36 Comment(1)
where is you "animation" variable declaration ?Gooden
G
3

Try to use more than 360 to avoid restarting.

I use 3600 insted of 360 and this works fine for me:

<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:toDegrees="3600"
    android:interpolator="@android:anim/linear_interpolator"
    android:repeatCount="infinite"
    android:duration="8000"
    android:pivotX="50%"
    android:pivotY="50%" />
Geyser answered 14/11, 2017 at 15:8 Comment(0)
V
1

Is it possible that because you go from 0 to 360, you spend a little bit more time at 0/360 than you are expecting? Perhaps set toDegrees to 359 or 358.

Volin answered 28/10, 2009 at 0:10 Comment(1)
Great theory. I'm pretty sure it's not that because the speedup/slowdown is quite smooth and deliberate looking. Just in case though I tried decreasing the degrees to 358 and there was no discernible change in behavior.Pozzy
C
1
private fun rotateTheView(view: View?, startAngle: Float, endAngle: Float) {
    val rotate = ObjectAnimator.ofFloat(view, "rotation", startAngle, endAngle)
    //rotate.setRepeatCount(10);
    rotate.duration = 400
    rotate.start()
}
Commodity answered 10/3, 2020 at 6:32 Comment(1)
While this code may provide a solution to the question, it's better to add context as to why/how it works. This can help future users learn, and apply that knowledge to their own code. You are also likely to have positive feedback from users in the form of upvotes, when the code is explained.Towns
C
1

you can use this code:

view.animate().rotation(360.0f).setDuration(1000);
Cockpit answered 26/11, 2021 at 12:19 Comment(0)
P
1

The 100% correct answer

    var rotate = RotateAnimation(
            0F, 360F,
            Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f
    )

  rotate.duration = 999
        rotate.repeatCount = Animation.INFINITE

   imageview..setAnimation(rotate)

100% working!!!

Papeete answered 4/7, 2022 at 9:21 Comment(1)
When the imageView is inside recyclerview, sometimes animation is not working for some of the items in recyclerview after scrolling. Any idea on how to fix it ?Kutenai
F
0

In Android, if you want to animate an object and make it move an object from location1 to location2, the animation API figures out the intermediate locations (tweening) and then queues onto the main thread the appropriate move operations at the appropriate times using a timer. This works fine except that the main thread is usually used for many other things — painting, opening files, responding to user inputs etc. A queued timer can often be delayed. Well written programs will always try to do as many operations as possible in background (non main) threads however you can’t always avoid using the main thread. Operations that require you to operate on a UI object always have to be done on the main thread. Also, many APIs will funnel operations back to the main thread as a form of thread-safety.

Views are all drawn on the same GUI thread which is also used for all user interaction.

So if you need to update GUI rapidly or if the rendering takes too much time and affects user experience then use SurfaceView.

Example of rotation image:

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
    private DrawThread drawThread;

    public MySurfaceView(Context context) {
        super(context);
        getHolder().addCallback(this);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {   
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        drawThread = new DrawThread(getHolder(), getResources());
        drawThread.setRunning(true);
        drawThread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        boolean retry = true;
        drawThread.setRunning(false);
        while (retry) {
            try {
                drawThread.join();
                retry = false;
            } catch (InterruptedException e) {
            }
        }
    }
}


class DrawThread extends Thread{
    private boolean runFlag = false;
    private SurfaceHolder surfaceHolder;
    private Bitmap picture;
    private Matrix matrix;
    private long prevTime;

    public DrawThread(SurfaceHolder surfaceHolder, Resources resources){
        this.surfaceHolder = surfaceHolder;

        picture = BitmapFactory.decodeResource(resources, R.drawable.icon);

        matrix = new Matrix();
        matrix.postScale(3.0f, 3.0f);
        matrix.postTranslate(100.0f, 100.0f);

        prevTime = System.currentTimeMillis();
    }

    public void setRunning(boolean run) {
        runFlag = run;
    }

    @Override
    public void run() {
        Canvas canvas;
        while (runFlag) {
            long now = System.currentTimeMillis();
            long elapsedTime = now - prevTime;
            if (elapsedTime > 30){

                prevTime = now;
                matrix.preRotate(2.0f, picture.getWidth() / 2, picture.getHeight() / 2);
            }
            canvas = null;
            try {
                canvas = surfaceHolder.lockCanvas(null);
                synchronized (surfaceHolder) {
                    canvas.drawColor(Color.BLACK);
                    canvas.drawBitmap(picture, matrix, null);
                }
            } 
            finally {
                if (canvas != null) {
                    surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
        }
    }
}

activity:

public class SurfaceViewActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new MySurfaceView(this));
    }
}
Ferrule answered 19/5, 2015 at 6:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.