¿Cómo hacer una rotación de imagen suave en Android?

217

Estoy usando a RotateAnimationpara rotar una imagen que estoy usando como un spinner cíclico personalizado en Android. Aquí está mi rotate_indefinitely.xmlarchivo, que coloqué en 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" />    

Cuando aplico esto a mi ImageViewuso AndroidUtils.loadAnimation(), ¡funciona muy bien!

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

El único problema es que la rotación de la imagen parece detenerse en la parte superior de cada ciclo.

En otras palabras, la imagen gira 360 grados, se detiene brevemente, luego gira 360 grados nuevamente, etc.

Sospecho que el problema es que la animación está usando un interpolador predeterminado como android:iterpolator="@android:anim/accelerate_interpolator"( AccelerateInterpolator), pero no sé cómo decirle que no interpole la animación.

¿Cómo puedo desactivar la interpolación (si ese es realmente el problema) para hacer que mi animación circule sin problemas?

emmby
fuente

Respuestas:

199

Tienes razón sobre AccelerateInterpolator; deberías usar LinearInterpolator en su lugar.

Puede usar el incorporado android.R.anim.linear_interpolatordesde su archivo XML de animación con android:interpolator="@android:anim/linear_interpolator".

O puede crear su propio archivo de interpolación XML en su proyecto, por ejemplo, asígnele el nombre res/anim/linear_interpolator.xml:

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

Y agregue a su animación XML:

android:interpolator="@anim/linear_interpolator"

Nota especial: si su animación de rotación está dentro de un conjunto, la configuración del interpolador no parece funcionar. Haciendo girar el elemento superior lo arregla. (esto te ahorrará tiempo)

Bakhtiyor
fuente
1
Esto se debe a que el interpolador se deletreó mal (no "n"). No necesita hacer el suyo propio
Kingpin
25
He probado todos los interpoladores disponibles, incluido el lineal, y todavía consigo este pequeño "enganche" al comienzo de cada ciclo.
Adam Rabung el
11
Si su animación de rotación está dentro de un conjunto, parece que configurar el interpolador no funciona. Hacer que gire el elemento superior lo arregla
shalafi
Oye, ¿qué pasa si realmente quieres usar accelerate_decelerate_interpolator sin la pequeña "pausa" entre cada animación?
agonist_
90

También tuve este problema e intenté configurar el interpolador lineal en xml sin éxito. La solución que funcionó para mí fue crear la animación como RotateAnimation en código.

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
fuente
99
si desea que la animación permanezca en la orientación al final, agreguerotate.setFillAfter(true);
Fonix el
44
si desea que la animación permanezca permanentemente sin fin, agreguerotate.setRepeatCount(Animation.INFINITE);
ahmednabil88
38

Esto funciona 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" />

Para invertir la rotación:

<?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. Fernández
fuente
55
Para reversa, solo tiene repetir cuenta 2 y configure android: repeatMode = "reverse" - no es necesario tener dos archivos XML diferentes.
RoundSparrow hilltx
3
¿Por qué 358 y no 359 o 360?
Behelit
¿Dónde agregar este archivo a los recursos? ¿Es la animación un paquete separado para esto?
abggcv
30

Tal vez, algo como esto ayudará:

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

Por cierto, puedes rotar más de 360 ​​como:

imageView.animate().rotationBy(10000)...
Vitaly Zinchenko
fuente
Funcionando perfectamente, ¿imageView.clearAnimation () también limpiará el ejecutable?
XcodeNOOB
Funciona bien para iniciar la animación, pero después de que el inicio se puede ejecutar no se limpia con imageView.clearAnimation (), utilicé el evento de escucha táctil
Abhijit Jagtap
No se pueden detener estos runnables de forma independiente
Pantalones
@Pantalones a los que puede llamar con EndAction (esto) en alguna condición. Si la condición es falsa, withEndAction (esto) no se llamará, y la animación debería detenerse
Vitaly Zinchenko
30

Intente usar toDegrees="359"ya que 360 ​​° y 0 ° son iguales.

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

Prueba esto.

Ashraf Rahman
fuente
8

Objeto de rotación mediante programación.

// rotación en el sentido de las agujas del reloj :

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

// Rotación antihoraria:

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

view es objeto de su ImageView u otros widgets.

rotate.setRepeatCount (10); use para repetir su rotación.

500 es la duración de tu tiempo de animación.

Geet Thakur
fuente
6

¡Podar el elemento <set>que envolvió el elemento <rotate>resuelve el problema!

Gracias a Shalafi!

Entonces su Rotation_ccw.xml debería verse así:

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

No importa lo que intenté, no pude hacer que esto funcionara correctamente usando código (y setRotation) para una animación de rotación suave. Lo que terminé haciendo fue hacer que los cambios de grado fueran tan pequeños, que las pequeñas pausas son imperceptibles. Si no necesita hacer demasiadas rotaciones, el tiempo para ejecutar este ciclo es insignificante. El efecto es una rotación suave:

        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);
        }
no uniforme
fuente
¿Dónde está la declaración de la variable "animación"?
Rishabh Saxena
3

Como Hanry ha mencionado anteriormente, poner el revestimiento de iterpolator está bien. Pero si la rotación está dentro de un conjunto, debe poner android: shareInterpolator = "false" para que sea más 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>

Si Sharedinterpolator no es falso, el código anterior da problemas técnicos.

Rahul Agrawal
fuente
3

Si está utilizando una animación de conjunto como yo, debe agregar la interpolación dentro de la etiqueta de conjunto:

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

Eso funcionó para mí.

Kokusho
fuente
3

En 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
fuente
1

¿Es posible que debido a que pases de 0 a 360, pases un poco más de tiempo en 0/360 de lo que esperas? Quizás establecido en Grados en 359 o 358.

William Rose
fuente
1
Gran teoría Estoy bastante seguro de que no es así porque la aceleración / desaceleración es bastante suave y deliberada. Por si acaso intenté disminuir los grados a 358 y no hubo un cambio perceptible en el comportamiento.
emmby
1

Intente usar más de 360 ​​para evitar reiniciar.

Yo uso 3600 en lugar de 360 ​​y esto funciona bien para mí:

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

En Android, si desea animar un objeto y hacer que mueva un objeto de ubicación1 a ubicación2, la API de animación calcula las ubicaciones intermedias (interpolación) y luego pone en cola en el hilo principal las operaciones de movimiento apropiadas en los momentos apropiados usando un temporizador . Esto funciona bien, excepto que el hilo principal generalmente se usa para muchas otras cosas: pintar, abrir archivos, responder a las entradas del usuario, etc. Un temporizador en cola a menudo se puede retrasar. Los programas bien escritos siempre intentarán realizar tantas operaciones como sea posible en subprocesos en segundo plano (no principales), sin embargo, no siempre puede evitar usar el subproceso principal. Las operaciones que requieren que opere en un objeto UI siempre deben realizarse en el hilo principal. Además, muchas API canalizarán las operaciones al hilo principal como una forma de seguridad de hilo.

Las vistas se dibujan en el mismo hilo GUI que también se usa para toda la interacción del usuario.

Entonces, si necesita actualizar la GUI rápidamente o si el renderizado toma demasiado tiempo y afecta la experiencia del usuario, use SurfaceView.

Ejemplo de imagen de rotación:

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

actividad:

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
fuente
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
fuente
1
Si bien este código puede proporcionar una solución a la pregunta, es mejor agregar contexto sobre por qué / cómo funciona. Esto puede ayudar a los futuros usuarios a aprender y aplicar ese conocimiento a su propio código. También es probable que reciba comentarios positivos de los usuarios en forma de votos a favor, cuando se explique el código.
borchvm