Jak sprawnie obracać obraz w Androidzie?

217

Używam RotateAnimationdo obracania obrazu, którego używam jako niestandardowego cyklicznego pokrętła w Androidzie. Oto mój rotate_indefinitely.xmlplik, który umieściłem w 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" />    

Kiedy zastosuję to do mojego ImageViewużycia AndroidUtils.loadAnimation(), działa świetnie!

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

Jedynym problemem jest to, że obrót obrazu wydaje się zatrzymywać u góry każdego cyklu.

Innymi słowy, obraz obraca się o 360 stopni, zatrzymuje się na chwilę, a następnie ponownie obraca o 360 stopni itp.

Podejrzewam, że problem polega na tym, że animacja korzysta z domyślnego interpolatora, takiego jak android:iterpolator="@android:anim/accelerate_interpolator"( AccelerateInterpolator), ale nie wiem, jak powiedzieć jej, aby nie interpolowała animacji.

Jak mogę wyłączyć interpolację (jeśli to rzeczywiście jest problem), aby cykl animacji przebiegał płynnie?

emmby
źródło

Odpowiedzi:

199

Masz rację co do AccelerateInterpolator; zamiast tego należy użyć LinearInterpolator.

Możesz użyć wbudowanego android.R.anim.linear_interpolatorz pliku XML animacji za pomocą android:interpolator="@android:anim/linear_interpolator".

Możesz też utworzyć własny plik interpolacji XML w swoim projekcie, np. Nazwij go res/anim/linear_interpolator.xml:

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

I dodaj do animacji XML:

android:interpolator="@anim/linear_interpolator"

Uwaga specjalna: jeśli animacja obracania znajduje się w zestawie, ustawienie interpolatora nie działa. Wykonanie obrotu górnego elementu naprawia go. (pozwoli to zaoszczędzić Twój czas).

Bakhtiyor
źródło
1
Wynika to z niepoprawnej pisowni interpolatora (brak „n”). Nie musisz tworzyć własnego
Kingpin
25
Wypróbowałem każdy dostępny interpolator, w tym liniowy, i wciąż dostaję ten mały „zaczep” na początku każdego cyklu.
Adam Rabung,
11
Jeśli animacja obracania znajduje się w zestawie, ustawienie interpolatora nie działa.
Naprawienie
Hej, co jeśli rzeczywiście chcesz użyć przyspieszania_sprzęgania_spolnika bez małej „pauzy” między każdą animacją?
agonist_
90

Miałem również ten problem i bez powodzenia próbowałem ustawić interpolator liniowy w xml. Rozwiązaniem, które działało dla mnie, było utworzenie animacji jako RotateAnimation w kodzie.

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.
źródło
9
jeśli chcesz, aby animacja pozostała w orientacji na końcu, dodajrotate.setFillAfter(true);
Fonix
4
jeśli chcesz, aby animacja pozostała na stałe bez końca, dodajrotate.setRepeatCount(Animation.INFINITE);
ahmednabil88 21.02.19
38

To działa dobrze

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

Aby odwrócić obrót:

<?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
źródło
5
W przypadku rewersu wystarczy tylko powtórzyć 2 i ustawić Androida: repeatMode = "reverse" - nie trzeba mieć dwóch różnych plików XML.
RoundSparrow hilltx
3
dlaczego 358, a nie 359 lub 360?
behelit
Gdzie dodać ten plik do zasobów? Czy animacja jest do tego osobnym pakietem?
abggcv
30

Może coś takiego pomoże:

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

Nawiasem mówiąc, możesz obracać o ponad 360, jak:

imageView.animate().rotationBy(10000)...
Witalij Zinchenko
źródło
Działa idealnie, czy imageView.clearAnimation () wyczyści także program uruchamialny?
XcodeNOOB,
Działa dobrze dla animacji startowej, ale po uruchomieniu nie jest czyszczony przez imageView.clearAnimation (), użyłem w zdarzeniu detektora dotykowego
Abhijit Jagtap
Nie można zatrzymać tych runnabeli niezależnie
Pants
@Pants możesz zadzwonić za pomocą EndAction (this) pod pewnymi warunkami. Jeśli warunek jest fałszywy, funkcjaEndAction (this) nie zostanie wywołana, a animacja powinna się zatrzymać
Witalij Zinchenko
30

Spróbuj użyć, toDegrees="359"ponieważ 360 ° i 0 ° są takie same.

Fernando Gallego
źródło
19
ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).setDuration(300).start();

Spróbuj tego.

Ashraf Rahman
źródło
8

Obiekt rotacyjny programowo.

// zgodnie z ruchem wskazówek zegara :

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

// Obrót w lewo:

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

widok jest przedmiotem twojego ImageView lub innych widżetów.

rotate.setRepeatCount (10); użyj, aby powtórzyć obrót.

500 to czas trwania animacji.

Geet Thakur
źródło
6

Przycinanie <set>elementu, który owinął<rotate> -Element, rozwiązuje problem!

Dzięki Shalafi!

Więc Twój plik Rotation_ccw.xml powinien wyglądać następująco:

<?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
źródło
3

Bez względu na to, co próbowałem, nie mogłem tego poprawnie uruchomić za pomocą kodu (i setRotation) do płynnej animacji obrotu. Skończyło się na tym, że zmiany stopni były tak małe, że małe przerwy były niezauważalne. Jeśli nie trzeba wykonywać zbyt wielu obrotów, czas wykonania tej pętli jest znikomy. Efektem jest płynny obrót:

        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);
        }
niejednolity
źródło
gdzie jest deklaracja zmiennej „animacja”?
Rishabh Saxena
3

Jak wspomniano powyżej Hanry, umieszczenie wkładki iterpolator jest w porządku. Ale jeśli rotacja jest w zestawie, musisz ustawić Androida: shareInterpolator = "false", aby było gładkie.

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

Jeśli Sharedinterpolator nie jest fałszywy, powyższy kod powoduje usterki.

Rahul Agrawal
źródło
3

Jeśli używasz zestawu animacji takiego jak ja, powinieneś dodać interpolację wewnątrz tagu zestawu:

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

To działało dla mnie.

Kokusho
źródło
3

W Kotlinie:

 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
źródło
1

Czy to możliwe, że ponieważ przechodzisz od 0 do 360, spędzasz trochę więcej czasu w 0/360, niż się spodziewasz? Być może ustaw na Stopnie na 359 lub 358.

William Rose
źródło
1
Wielka teoria. Jestem pewien, że tak nie jest, ponieważ przyspieszenie / spowolnienie jest dość płynne i celowe. Na wszelki wypadek próbowałem zmniejszyć stopnie do 358 i nie było zauważalnej zmiany w zachowaniu.
emmby 28.10.2009
1

Spróbuj użyć więcej niż 360, aby uniknąć ponownego uruchomienia.

Używam 3600 insted z 360 i to działa dobrze dla mnie:

<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
źródło
0

W Androidzie, jeśli chcesz animować obiekt i zmusić go do przeniesienia obiektu z lokalizacji 1 do lokalizacji 2, interfejs API animacji wykrywa pośrednie lokalizacje (animacja), a następnie kolejkuje w głównym wątku odpowiednie operacje przesuwania w odpowiednich momentach za pomocą timera . Działa to dobrze, z tym wyjątkiem, że główny wątek jest zwykle używany do wielu innych rzeczy - malowania, otwierania plików, reagowania na dane wejściowe użytkownika itp. Czasomierz w kolejce może często być opóźniany. Dobrze napisane programy zawsze będą próbowały wykonać jak najwięcej operacji w wątkach działających w tle (innych niż główne), jednak nie zawsze można uniknąć używania głównego wątku. Operacje wymagające operacji na obiekcie interfejsu użytkownika zawsze muszą być wykonywane w głównym wątku. Ponadto wiele interfejsów API przeniesie operacje z powrotem do głównego wątku jako formę zabezpieczenia wątku.

Widoki są rysowane w tym samym wątku GUI, który jest również używany do wszystkich interakcji użytkownika.

Więc jeśli musisz szybko zaktualizować GUI lub jeśli renderowanie zajmuje zbyt dużo czasu i wpływa na wrażenia użytkownika, użyj SurfaceView.

Przykład obrotu obrazu:

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

czynność:

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
źródło
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
źródło
1
Chociaż ten kod może dostarczyć rozwiązania pytania, lepiej jest dodać kontekst wyjaśniający, dlaczego / jak to działa. Może to pomóc przyszłym użytkownikom w nauce i zastosowaniu tej wiedzy do własnego kodu. Prawdopodobnie będziesz mieć pozytywne opinie od użytkowników w postaci pozytywnych opinii, gdy kod zostanie wyjaśniony.
borchvm