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?
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.)
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);
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" />
Essayez d’utiliser toDegrees="359"
puisque 360 ° et 0 ° sont identiques.
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)...
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"
/>
ObjectAnimatior.ofFloat(view,"rotation",0,360).start();
Essaye ça.
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.
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);
}
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.
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.
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.
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)
})
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));
}
}
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%" />