web-dev-qa-db-fra.com

Comment faire une rotation d'image en douceur dans Android?

J'utilise une variable RotateAnimation pour faire pivoter une image que j'utilise en tant que filtre cyclique personnalisé dans Android. Voici mon fichier rotate_indefinitely.xml 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 à ma ImageView en utilisant 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'interrompre au début de chaque cycle.

En d'autres termes, l'image pivote à 360 degrés, fait une pause brève, puis pivote à nouveau à 360 degrés, etc.

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

Comment puis-je désactiver l'interpolation (si c'est effectivement le problème) pour que mon animation se répète sans heurts?

185
emmby

Vous avez raison sur AccelerateInterpolator; vous devriez utiliser LinearInterpolator à la place.

Vous pouvez utiliser le Android.R.anim.linear_interpolator intégré à partir de 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. nommez-le 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 pivoter l'élément supérieur le corrige. (cela vous fera gagner du temps.)

185
Bakhtiyor

J'ai eu ce problème aussi, et j'ai essayé de définir l'interpolateur linéaire en xml sans succès. La solution qui a fonctionné pour moi était de créer l'animation sous forme de 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);
63
Rabs G

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" />
32
Luis E. Fernandez

Essayez d’utiliser toDegrees="359" puisque 360 ​​° et 0 ° sont identiques.

27
Fernando Gallego

Peut-être que quelque chose comme ça va aider:

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

À propos, vous pouvez faire plus de 360 ​​rotations comme:

imageView.animate().rotationBy(10000)...
27
Vitaly Zinchenko

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

Merci à Shalafi!

Donc, votre Rotation_ccw.xml devrait 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"
    />
6
Christopher Stock
ObjectAnimatior.ofFloat(view,"rotation",0,360).start();

Essaye ça.

4
Ashraf Rahman

Comme Hans l'a mentionné ci-dessus, mettre un liner iterpolator est correct. Mais si la rotation est dans un ensemble, vous devez mettre Android: shareInterpolator = "false" pour le rendre lisse.

<?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 false, le code ci-dessus donne des problèmes.

3
Rahul Agrawal

Quoi que j'aie essayé, je ne pouvais pas que cela fonctionne correctement en utilisant du code (et setRotation) pour une animation en rotation fluide. Ce que j’ai fini par faire, c’était de 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 nécessaire pour exécuter cette boucle est négligeable. L'effet est une rotation en douceur:

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

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

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

2
Kokusho

Est-il possible que, du fait que vous passiez de 0 à 360, vous passiez un peu plus de temps que prévu à 0/360? Peut-être définir toDegrees à 359 ou 358.

1
William Rose

Objet de rotation par programmation.

// rotation horaire : 

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

// Rotation anti-horaire:

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

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

0
Geet Thakur

À 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)
        })
0
Hitesh Sahu

Sous Android, si vous souhaitez animer un objet et le faire déplacer d'objet d'emplacement1 à emplacement2, l'API d'animation détermine les emplacements intermédiaires (interpolation), puis met en file d'attente sur le fil principal les opérations de déplacement appropriées aux moments appropriés à l'aide d'un minuteur. . Cela fonctionne bien, sauf que le fil principal est généralement utilisé pour beaucoup d'autres choses - peindre, ouvrir des fichiers, répondre aux entrées de l'utilisateur, etc. Un minuteur 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 pourrez pas toujours éviter d’utiliser le thread principal. Les opérations qui nécessitent que vous agissiez 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 thread-safety.

Les vues sont toutes dessinées sur le même thread d'interface graphique, qui est également utilisé pour toutes les interactions de l'utilisateur.

Donc, si vous devez mettre à jour l'interface graphique rapidement 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));
    }
}
0
phnmnn

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

J'utilise 3600 au lieu 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%" />
0
fisher3421