Wie mache ich eine reibungslose Bildrotation in Android?

217

Ich verwende a RotateAnimation, um ein Bild zu drehen, das ich als benutzerdefinierten zyklischen Spinner in Android verwende. Hier ist meine rotate_indefinitely.xmlDatei, in die ich gelegt habe 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" />    

Wenn ich dies auf meine ImageViewVerwendung anwende AndroidUtils.loadAnimation(), funktioniert es großartig!

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

Das einzige Problem ist, dass die Bilddrehung am Anfang jedes Zyklus anzuhalten scheint.

Mit anderen Worten, das Bild dreht sich um 360 Grad, hält kurz an und dreht sich dann wieder um 360 Grad usw.

Ich vermute, dass das Problem darin besteht, dass die Animation einen Standardinterpolator wie android:iterpolator="@android:anim/accelerate_interpolator"( AccelerateInterpolator) verwendet, aber ich weiß nicht, wie ich ihm sagen soll, dass die Animation nicht interpoliert werden soll.

Wie kann ich die Interpolation deaktivieren (wenn dies tatsächlich das Problem ist), um meinen Animationszyklus reibungslos zu gestalten?

emmby
quelle

Antworten:

199

Sie haben Recht mit AccelerateInterpolator; Sie sollten stattdessen LinearInterpolator verwenden.

Sie können das in android.R.anim.linear_interpolatorIhrer Animations-XML-Datei integrierte mit verwenden android:interpolator="@android:anim/linear_interpolator".

Oder Sie können Ihre eigene XML-Interpolationsdatei in Ihrem Projekt erstellen, z. B. benennen Sie sie res/anim/linear_interpolator.xml:

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

Und fügen Sie Ihrer Animation XML hinzu:

android:interpolator="@anim/linear_interpolator"

Besonderer Hinweis: Wenn sich Ihre Rotationsanimation innerhalb eines Sets befindet, scheint das Einstellen des Interpolators nicht zu funktionieren. Durch Drehen des oberen Elements wird das Problem behoben. (Dies spart Zeit.)

Bakhtiyor
quelle
1
Dies liegt daran, dass der Interpolator falsch geschrieben wurde (kein "n"). Sie müssen nicht Ihre eigenen machen
Kingpin
25
Ich habe jeden verfügbaren Interpolator ausprobiert, einschließlich des linearen, und ich bekomme immer noch dieses kleine "Problem" zu Beginn jedes Zyklus.
Adam Rabung
11
Wenn sich Ihre Rotationsanimation innerhalb eines Sets befindet, scheint das Einstellen des Interpolators nicht zu funktionieren. Das Drehen des oberen Elements behebt es
Shalafi
Hey, was ist, wenn Sie den Accelerate_Decelerate_Interpolator ohne die kleine "Pause" zwischen den einzelnen Animationen verwenden möchten?
Agonist_
90

Ich hatte auch dieses Problem und versuchte erfolglos, den linearen Interpolator in XML zu setzen. Die Lösung, die für mich funktioniert hat, bestand darin, die Animation als RotateAnimation im Code zu erstellen.

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.
quelle
9
Wenn Sie möchten, dass die Animation am Ende in der Ausrichtung rotate.setFillAfter(true);
bleibt
4
Wenn Sie möchten, dass die Animation dauerhaft ohne Ende rotate.setRepeatCount(Animation.INFINITE);
bleibt
38

Das funktioniert gut

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

Umkehren rückwärts drehen:

<?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
quelle
5
Für die Umkehrung haben Sie einfach repeatcount 2 und setzen Sie android: repeatMode = "reverse" - Sie müssen nicht zwei verschiedene XML-Dateien haben.
RoundSparrow Hilltx
3
warum 358 und nicht 359 oder 360?
siehe
Wo kann diese Datei in Ressourcen hinzugefügt werden? Ist Animation ein separates Paket dafür?
abggcv
30

Vielleicht hilft so etwas:

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

Übrigens können Sie um mehr als 360 drehen wie:

imageView.animate().rotationBy(10000)...
Vitaly Zinchenko
quelle
Funktioniert imageView.clearAnimation () auch die ausführbare Datei?
XcodeNOOB
Es funktioniert gut für die Startanimation, aber nach dem Start kann es nicht von imageView.clearAnimation () bereinigt werden. Ich habe es im Touch Listener Event verwendet
Abhijit Jagtap
Ich kann diese Runnables nicht unabhängig voneinander stoppen
Pants
@Pants können Sie unter bestimmten Bedingungen mit EndAction (this) aufrufen. Wenn die Bedingung falsch ist, wird withEndAction (dies) nicht aufgerufen und die Animation sollte gestoppt werden
Vitaly Zinchenko
30

Versuchen Sie es mit, toDegrees="359"da 360 ° und 0 ° gleich sind.

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

Versuche dies.

Ashraf Rahman
quelle
8

Rotationsobjekt programmgesteuert.

// Drehung im Uhrzeigersinn:

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

// Drehung gegen den Uhrzeigersinn:

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

Ansicht ist ein Objekt Ihrer ImageView oder anderer Widgets.

rotate.setRepeatCount (10); Verwenden Sie diese Option, um Ihre Rotation zu wiederholen.

500 ist Ihre Animationszeitdauer.

Geet Thakur
quelle
6

Beschneiden des <set>Elements, das das Element umhüllt hat<rotate> Element hat, löst das Problem!

Danke an Shalafi!

Ihre Rotation_ccw.xml sollte also so aussehen:

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

Egal was ich versuchte, ich konnte dies nicht richtig zum Laufen bringen, indem ich Code (und setRotation) für eine reibungslose Rotationsanimation verwendete. Am Ende habe ich die Gradänderungen so klein gemacht, dass die kleinen Pausen nicht wahrgenommen werden. Wenn Sie nicht zu viele Umdrehungen ausführen müssen, ist die Zeit zum Ausführen dieser Schleife vernachlässigbar. Der Effekt ist eine gleichmäßige Rotation:

        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);
        }
ungleichmäßig
quelle
Wo ist Ihre Variablenvariation "Animation"?
Rishabh Saxena
3

Wie hanry oben erwähnt hat, ist das Setzen des Liner-Iterpolators in Ordnung. Aber wenn die Rotation innerhalb eines Satzes liegt, müssen Sie android: shareInterpolator = "false" setzen, um es glatt zu machen.

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

Wenn Sharedinterpolator nicht falsch ist, gibt der obige Code Störungen.

Rahul Agrawal
quelle
3

Wenn Sie eine Set-Animation wie ich verwenden, sollten Sie die Interpolation innerhalb des Set-Tags hinzufügen:

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

Das hat bei mir funktioniert.

Kokusho
quelle
3

In 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
quelle
1

Ist es möglich, dass Sie, weil Sie von 0 auf 360 wechseln, etwas mehr Zeit bei 0/360 verbringen, als Sie erwarten? Vielleicht auf Grad auf 359 oder 358 setzen.

William Rose
quelle
1
Großartige Theorie. Ich bin mir ziemlich sicher, dass es nicht so ist, weil die Beschleunigung / Verlangsamung ziemlich flüssig ist und absichtlich aussieht. Nur für den Fall, dass ich versucht habe, die Grade auf 358 zu verringern, und es keine erkennbare Verhaltensänderung gab.
Emmby
1

Versuchen Sie, mehr als 360 zu verwenden, um einen Neustart zu vermeiden.

Ich benutze 3600 insted von 360 und das funktioniert gut für mich:

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

Wenn Sie in Android ein Objekt animieren und von Objekt1 zu Standort2 verschieben möchten, ermittelt die Animations-API die Zwischenpositionen (Tweening) und stellt die entsprechenden Verschiebungsvorgänge zu den entsprechenden Zeiten mithilfe eines Timers in den Hauptthread . Dies funktioniert einwandfrei, außer dass der Haupt-Thread normalerweise für viele andere Dinge verwendet wird - Malen, Öffnen von Dateien, Reagieren auf Benutzereingaben usw. Ein Timer in der Warteschlange kann häufig verzögert werden. Gut geschriebene Programme versuchen immer, so viele Operationen wie möglich in Hintergrundthreads (nicht Hauptthreads) auszuführen. Sie können jedoch nicht immer vermeiden, den Hauptthread zu verwenden. Vorgänge, bei denen Sie ein UI-Objekt bearbeiten müssen, müssen immer im Hauptthread ausgeführt werden. Außerdem leiten viele APIs Operationen als eine Form der Thread-Sicherheit zurück zum Haupt-Thread.

Alle Ansichten werden auf demselben GUI-Thread gezeichnet, der auch für alle Benutzerinteraktionen verwendet wird.

Wenn Sie also die GUI schnell aktualisieren müssen oder wenn das Rendern zu lange dauert und die Benutzererfahrung beeinträchtigt, verwenden Sie SurfaceView.

Beispiel für ein Rotationsbild:

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

Aktivität:

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
quelle
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
quelle
1
Während dieser Code eine Lösung für die Frage bietet, ist es besser, einen Kontext hinzuzufügen, warum / wie er funktioniert. Dies kann zukünftigen Benutzern helfen, zu lernen und dieses Wissen auf ihren eigenen Code anzuwenden. Es ist auch wahrscheinlich, dass Sie positive Rückmeldungen von Benutzern in Form von Upvotes erhalten, wenn der Code erklärt wird.
Borchvm