Como fazer uma rotação de imagem suave no Android?
Pergunta
Eu estou usando um RotateAnimation
para girar uma imagem que eu estou usando como um spinner cíclica personalizada no Android. Aqui está o meu arquivo rotate_indefinitely.xml
, que I colocado em 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 eu aplicar este a minha ImageView
usando 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 fazer uma pausa no topo de cada ciclo.
Em outras palavras, a rotação da imagem 360 graus, faz uma breve pausa, 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 eu não sei como dizer não para interpolar a animação.
Como posso desligar a interpolação (se isso é realmente o problema) para fazer o meu ciclo de animação sem problemas?
Solução
Você está certo sobre AccelerateInterpolator; você deve usar LinearInterpolator vez.
Você pode usar o built-in android.R.anim.linear_interpolator
de seu arquivo XML animação com android:interpolator="@android:anim/linear_interpolator"
.
Ou você pode criar seu próprio arquivo de interpolação XML em seu projeto, por exemplo, nomeá-lo res/anim/linear_interpolator.xml
:
<?xml version="1.0" encoding="utf-8"?>
<linearInterpolator xmlns:android="http://schemas.android.com/apk/res/android" />
E adicionar à sua XML animação:
android:interpolator="@anim/linear_interpolator"
Nota especial: Se a sua animação de rotação está dentro de um conjunto, definindo o interpolador não parece trabalho. Fazer o Gire a parte superior correções elemento de TI. (Isso irá poupar seu tempo.)
Outras dicas
Eu tive esse problema também, e tentou definir o interpolador linear em xml sem sucesso. A solução que funcionou para mim foi a de criar a animação como um 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);
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 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" />
Talvez, algo como isso vai ajudar:
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 por mais de 360 ??como:
imageView.animate().rotationBy(10000)...
Tente usar toDegrees="359"
desde 360 ??° e 0 ° são o mesmo.
Poda o <set>
-Elemento que envolveu a <rotate>
-Elemento resolve o problema!
Graças à Shalafi!
Portanto, o seu Rotation_ccw.xml deve loook 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"
/>
ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).setDuration(300).start();
Tente isto.
Não importa o que eu tentei, eu não poderia chegar a este trabalho direito usando o código (e setRotation) para a animação rotação suave. O que eu acabei fazendo estava fazendo o grau muda tão pequena, que as pequenas pausas são imperceptíveis. Se você não precisa fazer muitas rotações, o tempo para executar este ciclo é 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);
}
Como Hanry foi mencionado acima colocando forro iterpolator é bom. Mas se a rotação está 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 Sharedinterpolator não sendo falsa, o código acima dá falhas.
Rotação de objeto de programação.
// rotação no sentido horário:
public void rorate_Clockwise(View view) {
ObjectAnimator rotate = ObjectAnimator.ofFloat(view, "rotation", 180f, 0f);
// rotate.setRepeatCount(10);
rotate.setDuration(500);
rotate.start();
}
rotação // sentido anti-horário:
public void rorate_AntiClockwise(View view) {
ObjectAnimator rotate = ObjectAnimator.ofFloat(view, "rotation", 0f, 180f);
// rotate.setRepeatCount(10);
rotate.setDuration(500);
rotate.start();
}
Ver é objeto do seu ImageView ou outros widgets.
rotate.setRepeatCount (10);. uso de repetir sua rotação
500 é o tempo de duração da animação.
Se você estiver usando uma Animação conjunto como eu, você deve adicionar 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>
que funcionou para mim.
É possível que porque você ir de 0 a 360, você gastar um pouco mais tempo em 0/360 do que você está esperando? Talvez definir toDegrees a 359 ou 358.
Em Android, se você quiser animar um objeto e torná-lo mover um objeto de location1 para location2, as figuras de animação API fora os locais intermédios (interpolação) e depois filas para o segmento principal das operações de movimentação apropriados nos momentos apropriados utilizando um temporizador. Isso funciona bem, exceto que o segmento principal é normalmente usado para muitas outras coisas - pintura, abrir arquivos, respondendo ao usuário insumos etc. A fila de espera temporizador muitas vezes pode ser adiada. programas bem escritos sempre tentar fazer o maior número de operações quanto possível no fundo (não principais) tópicos, contudo, você não pode sempre evitar usar o segmento principal. Operações que exigem que você operar em um objeto de UI sempre tem que ser feito no segmento principal. Além disso, muitas APIs vai canalizar operações de volta para o segmento principal, como uma forma de thread-segurança.
As visualizações são todos desenhados no mesmo segmento GUI que também é usado para todos os interação do usuário.
Então, se você precisa de atualização GUI rapidamente ou se a prestação leva muito tempo e afeta a experiência do usuário, em seguida, usar SurfaceView.
Exemplo 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));
}
}
Tente usar mais de 360 ??para evitar a reinicialização.
Eu uso 3600 insted de 360 ??e esta multa funciona 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%" />
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)
})