Come effettuare una rotazione uniforme delle immagini in Android?


217

Sto usando a RotateAnimationper ruotare un'immagine che sto usando come spinner ciclico personalizzato in Android. Ecco il mio rotate_indefinitely.xmlfile, in cui ho inserito 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" />    

Quando applico questo al mio ImageViewutilizzo AndroidUtils.loadAnimation(), funziona benissimo!

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

L'unico problema è che la rotazione dell'immagine sembra fermarsi all'inizio di ogni ciclo.

In altre parole, l'immagine ruota di 360 gradi, si ferma brevemente, quindi ruota di nuovo di 360 gradi, ecc.

Sospetto che il problema sia che l'animazione utilizza un interpolatore predefinito come android:iterpolator="@android:anim/accelerate_interpolator"( AccelerateInterpolator), ma non so come dirgli di non interpolare l'animazione.

Come posso disattivare l'interpolazione (se questo è davvero il problema) per rendere il mio ciclo di animazione senza problemi?

Risposte:


199

Hai ragione su AccelerateInterpolator; dovresti usare invece LinearInterpolator.

È possibile utilizzare il file incorporato android.R.anim.linear_interpolatordal file XML di animazione con android:interpolator="@android:anim/linear_interpolator".

Oppure puoi creare il tuo file di interpolazione XML nel tuo progetto, ad esempio chiamalo res/anim/linear_interpolator.xml:

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

E aggiungi al tuo XML di animazione:

android:interpolator="@anim/linear_interpolator"

Nota speciale: se l'animazione di rotazione si trova all'interno di un set, l'impostazione dell'interpolatore non sembra funzionare. Facendo ruotare l'elemento superiore lo risolve. (questo ti farà risparmiare tempo.)


1
Questo perché l'interpolatore è stato digitato in modo errato (no "n"). Non è necessario crearne uno tuo
Kingpin,

25
Ho provato tutti gli interpolatori disponibili, incluso il lineare, e ho ancora questo piccolo "intoppo" all'inizio di ogni ciclo.
Adam Rabung,

11
Se l'animazione di rotazione si trova all'interno di un set, l'impostazione dell'interpolatore non sembra funzionare.
Riparare

Ehi, cosa succede se si desidera effettivamente utilizzare accelerate_decelerate_interpolator senza la piccola "pausa" tra ogni animazione?
agonist_

90

Ho avuto anche questo problema e ho provato a impostare l'interpolatore lineare in xml senza successo. La soluzione che ha funzionato per me è stata quella di creare l'animazione come RotateAnimation nel codice.

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

9
se vuoi che l'animazione mantenga l'orientamento alla fine, aggiungirotate.setFillAfter(true);
Fonix il

4
se vuoi che l'animazione rimanga permanentemente senza fine, aggiungirotate.setRepeatCount(Animation.INFINITE);
ahmednabil88,

38

Funziona benissimo

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

Per invertire la rotazione:

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

5
Per il contrario, basta ripetere repeatcount 2 e impostare android: repeatMode = "reverse" - non è necessario avere due diversi file XML.
RoundSparrow hilltx

3
perché 358 e non 359 o 360?
Behelit,

Dove aggiungere questo file nelle risorse? L'animazione è un pacchetto separato per questo?
abggcv,

30

Forse qualcosa del genere aiuterà:

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

A proposito, puoi ruotare di oltre 360 ​​come:

imageView.animate().rotationBy(10000)...

Funzionando perfettamente, imageView.clearAnimation () pulirà anche il file eseguibile?
XcodeNOOB,

Funziona benissimo per avviare l'animazione, ma dopo l'avvio eseguibile non viene ripulito da imageView.clearAnimation (), ho usato nell'evento listener touch
Abhijit Jagtap

Non riesco a fermare questi runnable in modo indipendente
Pantaloni

@Pants è possibile chiamare conEndAction (questo) a determinate condizioni. Se la condizione è falsa, withEndAction (this) non verrà chiamato e l'animazione dovrebbe fermarsi
Vitaly Zinchenko


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

Prova questo.


8

Oggetto di rotazione a livello di codice.

// rotazione in senso orario:

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

// Rotazione antioraria:

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

view è oggetto di ImageView o di altri widget.

rotate.setRepeatCount (10); usare per ripetere la rotazione.

500 è la durata del tempo di animazione.


6

Potare l' <set>Elemento che avvolge l' <rotate>Elemento risolve il problema!

Grazie a Shalafi!

Quindi il tuo Rotation_ccw.xml dovrebbe apparire così:

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

3

Indipendentemente da ciò che ho provato, non sono riuscito a farlo funzionare correttamente usando il codice (e setRotation) per l'animazione della rotazione regolare. Ciò che ho finito per fare è stato rendere i cambiamenti di grado così piccoli che le piccole pause sono impercettibili. Se non è necessario eseguire troppe rotazioni, il tempo per eseguire questo ciclo è trascurabile. L'effetto è una rotazione regolare:

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

dov'è la tua dichiarazione variabile "animazione"?
Rishabh Saxena,

3

Come hanry ha menzionato sopra, mettere l'iterpolatore del rivestimento va bene. Ma se la rotazione è all'interno di un set, devi mettere android: shareInterpolator = "false" per renderlo più fluido.

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

Se Sharedinterpolator non è falso, il codice sopra riportato dà problemi.


3

Se stai usando un'animazione set come me dovresti aggiungere l'interpolazione all'interno del tag 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>

Ha funzionato per me.


3

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

1

È possibile che, passando da 0 a 360, passi un po 'più di tempo a 0/360 di quanto ti aspetti? Forse impostato su Gradi su 359 o 358.


1
Grande teoria. Sono abbastanza sicuro che non sia perché l'accelerazione / il rallentamento sono piuttosto fluidi e deliberati. Nel caso in cui ho provato a ridurre i gradi a 358 e non ci sono stati cambiamenti evidenti nel comportamento.
emmby,

1

Prova a utilizzare più di 360 per evitare il riavvio.

Uso 3600 insted di 360 e questo funziona bene per 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%" />

0

In Android, se si desidera animare un oggetto e farlo spostare un oggetto da posizione1 a posizione2, l'API di animazione individua le posizioni intermedie (interpolazione) e quindi mette in coda sul thread principale le operazioni di spostamento appropriate nei momenti appropriati utilizzando un timer . Funziona bene, tranne per il fatto che il thread principale viene solitamente utilizzato per molte altre cose: disegnare, aprire file, rispondere agli input dell'utente, ecc. Un timer in coda può spesso essere ritardato. Programmi ben scritti cercheranno sempre di eseguire il maggior numero possibile di operazioni nei thread in background (non principali), tuttavia non è sempre possibile evitare di utilizzare il thread principale. Le operazioni che richiedono di operare su un oggetto UI devono sempre essere eseguite sul thread principale. Inoltre, molte API restituiranno le operazioni al thread principale come una forma di sicurezza del thread.

Le viste sono tutte disegnate sullo stesso thread della GUI che viene utilizzato anche per tutte le interazioni dell'utente.

Pertanto, se è necessario aggiornare la GUI rapidamente o se il rendering richiede troppo tempo e influisce sull'esperienza utente, utilizzare SurfaceView.

Esempio di immagine di rotazione:

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

attività:

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

1
Mentre questo codice può fornire una soluzione alla domanda, è meglio aggiungere un contesto sul perché / come funziona. Questo può aiutare gli utenti futuri a imparare e applicare tali conoscenze al proprio codice. È inoltre probabile che tu riceva un feedback positivo dagli utenti sotto forma di valutazioni, quando viene spiegato il codice.
Borchvm,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.