Comment faire une rotation d'image fluide dans Android?

217

J'utilise un RotateAnimationpour faire pivoter une image que j'utilise comme spinner cyclique personnalisé dans Android. Voici mon rotate_indefinitely.xmldossier, que j'ai placé dans 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" />    

Lorsque j'applique cela à mon ImageViewutilisation AndroidUtils.loadAnimation(), cela fonctionne très bien!

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

Le seul problème est que la rotation de l'image semble s'arrêter en haut de chaque cycle.

En d'autres termes, l'image pivote à 360 degrés, s'arrête brièvement, puis pivote à nouveau à 360 degrés, etc.

Je soupçonne que le problème est que l'animation utilise un interpolateur par défaut comme android:iterpolator="@android:anim/accelerate_interpolator"( AccelerateInterpolator), mais je ne sais pas comment lui dire de ne pas interpoler l'animation.

Comment désactiver l'interpolation (si tel est bien le problème) pour fluidifier mon cycle d'animation?

emmby
la source

Réponses:

199

Vous avez raison sur AccelerateInterpolator; vous devez plutôt utiliser LinearInterpolator.

Vous pouvez utiliser le intégré android.R.anim.linear_interpolatorde votre fichier XML d'animation avec android:interpolator="@android:anim/linear_interpolator".

Ou vous pouvez créer votre propre fichier d'interpolation XML dans votre projet, par exemple le nommer res/anim/linear_interpolator.xml:

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

Et ajoutez à votre animation XML:

android:interpolator="@anim/linear_interpolator"

Remarque spéciale: si votre animation de rotation est à l'intérieur d'un ensemble, le réglage de l'interpolateur ne semble pas fonctionner. Faire tourner l'élément supérieur le corrige. (cela vous fera gagner du temps.)

Bakhtiyor
la source
1
En effet, l'interpolateur a été mal orthographié (pas de "n"). Vous n'avez pas besoin de créer le vôtre
Kingpin
25
J'ai essayé tous les interpolateurs disponibles, y compris le linéaire, et je reçois toujours ce petit "accroc" au début de chaque cycle.
Adam Rabung
11
Si votre animation de rotation est à l'intérieur d'un ensemble, le réglage de l'interpolateur ne semble pas fonctionner. Faire pivoter l'élément supérieur le corrige
shalafi
Hé, que se passe-t-il si vous voulez réellement utiliser accelerate_decelerate_interpolator sans la petite "pause" entre chaque animation?
agonist_
90

J'ai également eu ce problème et j'ai essayé de définir l'interpolateur linéaire en XML sans succès. La solution qui a fonctionné pour moi a été de créer l'animation en tant que RotateAnimation dans le 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);
Rabs G
la source
9
si vous voulez que l'animation reste à l'orientation à la fin, ajoutezrotate.setFillAfter(true);
Fonix
4
si vous voulez que l'animation reste en permanence sans fin, ajoutezrotate.setRepeatCount(Animation.INFINITE);
ahmednabil88
38

Cela fonctionne bien

<?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" />

Pour inverser la rotation:

<?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" />
Luis E. Fernandez
la source
5
Pour inverser, il suffit de répéter le compte 2 et de définir android: repeatMode = "reverse" - pas besoin d'avoir deux fichiers XML différents.
RoundSparrow hilltx
3
pourquoi 358 et non 359 ou 360?
behelit
Où ajouter ce fichier dans les ressources? L'animation est-elle un package séparé pour cela?
abggcv
30

Peut-être que quelque chose comme ça aidera:

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();

Soit dit en passant, vous pouvez faire pivoter de plus de 360 ​​comme:

imageView.animate().rotationBy(10000)...
Vitaly Zinchenko
la source
Fonctionnant parfaitement, imageView.clearAnimation () nettoiera-t-elle également le runnable?
XcodeNOOB
Son fonctionne bien pour démarrer l'animation, mais après le lancement de runnable, il n'est pas nettoyé par imageView.clearAnimation (), j'ai utilisé dans l'événement d'écoute tactile
Abhijit Jagtap
Impossible d'arrêter ces runnables indépendamment
Pantalon
@Pants vous pouvez appeler withEndAction (this) à certaines conditions. Si la condition est fausse, withEndAction (ceci) ne sera pas appelé, et l'animation devrait s'arrêter
Vitaly Zinchenko
30

Essayez d'utiliser toDegrees="359"car 360 ° et 0 ° sont identiques.

Fernando Gallego
la source
19
ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).setDuration(300).start();

Essaye ça.

Ashraf Rahman
la source
8

Objet de rotation par programmation.

// rotation horaire :

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

// Rotation anti-horaire:

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

La vue est l'objet de votre ImageView ou d'autres widgets.

Rotate.setRepeatCount (10); utilisez pour répéter votre rotation.

500 est la durée de votre animation.

Geet Thakur
la source
6

L'élagage de <set>-Element qui a enveloppé <rotate>-Element résout le problème!

Merci à Shalafi!

Votre Rotation_ccw.xml devrait donc ressembler à ceci:

<?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"
    />
Christopher Stock
la source
3

Peu importe ce que j'ai essayé, je n'ai pas pu faire fonctionner cela correctement en utilisant du code (et setRotation) pour une animation de rotation fluide. J'ai fini par faire des changements de degré si petits que les petites pauses sont imperceptibles. Si vous n'avez pas besoin de faire trop de rotations, le temps pour exécuter cette boucle est négligeable. L'effet est une rotation douce:

        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);
        }
non uniforme
la source
où est votre déclaration de variable "animation"?
Rishabh Saxena
3

Comme hanry l'a mentionné ci-dessus, mettre l'itérolateur de doublure est très bien. Mais si la rotation est à l'intérieur d'un ensemble, vous devez mettre android: shareInterpolator = "false" pour le rendre plus fluide.

<?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>

Si Sharedinterpolator n'est pas faux, le code ci-dessus donne des problèmes.

Rahul Agrawal
la source
3

Si vous utilisez une animation d'ensemble comme moi, vous devez ajouter l'interpolation à l'intérieur de la balise d'ensemble:

<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>

Cela a fonctionné pour moi.

Kokusho
la source
3

À 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)
        })
Hitesh Sahu
la source
1

Est-il possible que parce que vous passez de 0 à 360, vous passez un peu plus de temps à 0/360 que ce que vous attendiez? Peut-être défini sur Degrés à 359 ou 358.

William Rose
la source
1
Grande théorie. Je suis sûr que ce n'est pas ça parce que l'accélération / le ralentissement est assez lisse et délibéré. Au cas où j'aurais essayé de réduire les degrés à 358 et qu'il n'y avait pas de changement de comportement perceptible.
emmby
1

Essayez d'utiliser plus de 360 ​​pour éviter de redémarrer.

J'utilise 3600 insted de 360 ​​et cela fonctionne très bien pour moi:

<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%" />
vmtrue
la source
0

Dans Android, si vous souhaitez animer un objet et le faire déplacer un objet de location1 à location2, l'API d'animation détermine les emplacements intermédiaires (interpolation), puis met en file d'attente sur le thread principal les opérations de déplacement appropriées aux moments appropriés à l'aide d'une minuterie . Cela fonctionne bien, sauf que le thread principal est généralement utilisé pour de nombreuses autres choses - peinture, ouverture de fichiers, réponse aux entrées de l'utilisateur, etc. Un temporisateur en file d'attente peut souvent être retardé. Les programmes bien écrits essaieront toujours de faire autant d'opérations que possible dans les threads d'arrière-plan (non principaux), mais vous ne pouvez pas toujours éviter d'utiliser le thread principal. Les opérations qui vous obligent à opérer sur un objet d'interface utilisateur doivent toujours être effectuées sur le thread principal. En outre, de nombreuses API redirigent les opérations vers le thread principal sous forme de sécurité des threads.

Les vues sont toutes dessinées sur le même thread GUI qui est également utilisé pour toutes les interactions utilisateur.

Donc, si vous devez mettre à jour rapidement l'interface graphique ou si le rendu prend trop de temps et affecte l'expérience utilisateur, utilisez SurfaceView.

Exemple d'image de rotation:

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);
                }
            }
        }
    }
}

activité:

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));
    }
}
phnmnn
la source
0
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()
}
TOUSIF AHAMMAD
la source
1
Bien que ce code puisse fournir une solution à la question, il est préférable d'ajouter du contexte pour expliquer pourquoi / comment cela fonctionne. Cela peut aider les futurs utilisateurs à apprendre et à appliquer ces connaissances à leur propre code. Vous êtes également susceptible d'avoir des commentaires positifs des utilisateurs sous la forme de votes positifs, lorsque le code est expliqué.
borchvm