Como fazer uma rotação suave da imagem no Android?

217

Estou usando um RotateAnimationpara girar uma imagem que estou usando como um girador cíclico personalizado no Android. Aqui está o meu rotate_indefinitely.xmlarquivo, que eu coloquei 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 aplico isso ao meu ImageViewuso AndroidUtils.loadAnimation(), ele funciona muito bem!

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

O único problema é que a rotação da imagem parece pausar na parte superior de cada ciclo.

Em outras palavras, a imagem gira 360 graus, pausa brevemente e depois gira 360 graus novamente, etc.

Eu suspeito que o problema é que a animação está usando um interpolador padrão como android:iterpolator="@android:anim/accelerate_interpolator"( AccelerateInterpolator), mas não sei como dizer para não interpolar a animação.

Como posso desativar a interpolação (se esse é realmente o problema) para fazer minha animação funcionar sem problemas?

empapar
fonte

Respostas:

199

Você está certo sobre o AccelerateInterpolator; você deve usar o LinearInterpolator.

Você pode usar o built-in android.R.anim.linear_interpolatordo seu arquivo XML de animação com android:interpolator="@android:anim/linear_interpolator".

Ou você pode criar seu próprio arquivo de interpolação XML no seu projeto, por exemplo, nomeie-o res/anim/linear_interpolator.xml:

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

E adicione à sua animação XML:

android:interpolator="@anim/linear_interpolator"

Nota especial: Se sua animação girar estiver dentro de um conjunto, a configuração do interpolador parece não funcionar. Fazer a rotação do elemento superior o corrige. (isso economizará seu tempo.)

Bakhtiyor
fonte
1
Isso ocorre porque o interpolador foi digitado errado (não "n"). Você não precisa fazer o seu próprio
Kingpin
25
Eu tentei todos os interpoladores disponíveis, incluindo o linear, e ainda recebo esse pequeno "engate" no início de cada ciclo.
Adam Rabung
11
Se sua animação girar estiver dentro de um conjunto, a configuração do interpolador parece não funcionar. Fazer o Gire a parte superior correções elemento-lo
shalafi
Ei, e se você realmente quiser usar o acelerate_decelerate_interpolator sem a pequena "pausa" entre todas as animações?
precisa saber é
90

Eu também tive esse problema e tentei definir o interpolador linear em xml sem sucesso. A solução que funcionou para mim foi criar a animação como uma RotateAnimation no 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
fonte
9
se você quiser que a animação permaneça na orientação no final, adicionerotate.setFillAfter(true);
Fonix
4
se você deseja que a animação para ficar permanentemente sem fim, adicionerotate.setRepeatCount(Animation.INFINITE);
ahmednabil88
38

Isso funciona bem

<?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 inverter a rotação:

<?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
fonte
5
Para reverter, basta repetir a contagem 2 e definir android: repeatMode = "reverse" - não é necessário ter dois arquivos XML diferentes.
RoundSparrow hilltx
3
por que 358 e não 359 ou 360?
behelit
Onde adicionar esse arquivo nos recursos? A animação é um pacote separado para isso?
Abggcv
30

Talvez algo como isto ajude:

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 propósito, você pode girar mais de 360 ​​como:

imageView.animate().rotationBy(10000)...
Vitaly Zinchenko
fonte
Funcionando perfeitamente, o imageView.clearAnimation () também limpa o executável?
XcodeNOOB
Sua multa trabalhando para animação início, mas depois de começar executável cabe não limpa por imageView.clearAnimation (), eu usei no evento de toque ouvinte
Abhijit Jagtap
Não pode parar esses Runnables independentemente
Calças
@Calças que você pode chamar com EndAction (this) em algumas condições. Se a condição for falsa, withEndAction (this) não será chamado, e animação deve parar
Vitaly Zinchenko
30

Tente usar, toDegrees="359"pois 360 ° e 0 ° são iguais.

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

Tente isso.

Ashraf Rahman
fonte
8

Objeto de rotação programaticamente.

// rotação no sentido horário :

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

// Rotação anti-horária:

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

view é objeto do seu ImageView ou de outros widgets.

rotate.setRepeatCount (10); use para repetir sua rotação.

500 é a duração do seu tempo de animação.

Geet Thakur
fonte
6

A <set>remoção do -Element que envolveu o <rotate>-Element resolve o problema!

Graças a Shalafi!

Portanto, seu Rotation_ccw.xml deve ficar assim:

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

Não importa o que eu tentei, não consegui fazer isso funcionar corretamente usando o código (e o setRotation) para uma animação de rotação suave. O que acabei fazendo foi fazer as mudanças de grau serem tão pequenas que as pequenas pausas são imperceptíveis. Se você não precisar fazer muitas rotações, o tempo para executar esse loop é insignificante. O efeito é uma rotação 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);
        }
não uniforme
fonte
onde está a declaração da variável "animação"?
Rishabh Saxena
3

Como Hanry mencionou acima, colocar o iterpolator do liner é bom. Mas se a rotação estiver dentro de um conjunto, você deve colocar android: shareInterpolator = "false" para torná-lo suave.

<?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 o Sharedinterpolator não for falso, o código acima indica falhas.

Rahul Agrawal
fonte
3

Se você estiver usando um conjunto de animação como eu, adicione a interpolação dentro da 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>

Isso funcionou para mim.

Kokusho
fonte
3

Em 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
fonte
1

É possível que, como você passe de 0 a 360, gaste um pouco mais de tempo em 0/360 do que esperava? Talvez definido como Graus para 359 ou 358.

William Rose
fonte
1
Grande teoria. Tenho certeza que não é isso porque a aceleração / desaceleração é bastante suave e deliberada. Apenas para o caso de eu tentar diminuir os graus para 358 e não houve mudança discernível no comportamento.
emmby
1

Tente usar mais de 360 ​​para evitar a reinicialização.

Eu uso 3600 insted de 360 ​​e isso funciona bem para mim:

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

No Android, se você deseja animar um objeto e fazê-lo mover um objeto de location1 para location2, a API de animação descobre os locais intermediários (interpolação) e enfileira no thread principal as operações de movimentação apropriadas nos horários apropriados, usando um timer . Isso funciona bem, exceto que o encadeamento principal geralmente é usado para muitas outras coisas - pintura, abertura de arquivos, resposta às entradas do usuário etc. Um timer em fila geralmente pode ser atrasado. Programas bem escritos sempre tentarão realizar o maior número possível de operações em threads em segundo plano (não principais), mas nem sempre é possível evitar o uso da thread principal. As operações que exigem que você opere em um objeto de interface do usuário sempre precisam ser realizadas no encadeamento principal. Além disso, muitas APIs direcionarão as operações de volta para o thread principal como uma forma de segurança de thread.

As visualizações são todas desenhadas no mesmo encadeamento da GUI, que também é usado para toda a interação do usuário.

Portanto, se você precisar atualizar a GUI rapidamente ou se a renderização demorar muito e afetar a experiência do usuário, use o SurfaceView.

Exemplo de imagem de rotação:

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

atividade:

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
fonte
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
fonte
1
Embora esse código possa fornecer uma solução para a pergunta, é melhor adicionar contexto ao porquê / como ele funciona. Isso pode ajudar os usuários futuros a aprender e aplicar esse conhecimento ao seu próprio código. Também é provável que você tenha um feedback positivo dos usuários na forma de upvotes, quando o código for explicado.
borchvm 10/03