Как сделать плавный поворот изображения в Android?

Я использую RotateAnimation для поворота изображения, которое я использую в качестве настраиваемого циклического прядильщика в Android. Вот мой rotate_indefinitely.xml файл, который я поместил в 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" />    

Когда я применяю это к своему ImageView, используя AndroidUtils.loadAnimation(), он отлично работает!

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

Единственная проблема заключается в том, что вращение изображения, кажется, приостанавливается в начале каждого цикла.

Другими словами, изображение поворачивается на 360 градусов, ненадолго останавливается, затем снова поворачивается на 360 градусов и т. Д.

Я подозреваю, что проблема в том, что анимация использует интерполятор по умолчанию, например android:iterpolator="@android:anim/accelerate_interpolator" (AccelerateInterpolator), но я не знаю, как сказать ему не интерполировать анимацию.

Как я могу отключить интерполяцию (если это действительно проблема), чтобы цикл анимации был плавным?


person emmby    schedule 27.10.2009    source источник


Ответы (16)


Вы правы насчет AccelerateInterpolator; вместо этого вам следует использовать LinearInterpolator.

Вы можете использовать встроенный android.R.anim.linear_interpolator из XML-файла анимации с android:interpolator="@android:anim/linear_interpolator".

Или вы можете создать свой собственный файл интерполяции XML в своем проекте, например назовите его res/anim/linear_interpolator.xml:

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

И добавьте в свою анимацию XML:

android:interpolator="@anim/linear_interpolator"

Особое примечание. Если ваша анимация поворота находится внутри набора, установка интерполятора не работает. Поворот верхнего элемента фиксирует это. (это сэкономит ваше время.)

person Bakhtiyor    schedule 28.10.2009
comment
Это потому, что интерполятор был написан неправильно (нет n). Вам не нужно делать свое собственное - person Kingpin; 18.02.2011
comment
Я перепробовал все доступные интерполяторы, включая линейный, и до сих пор получаю эту небольшую заминку в начале каждого цикла. - person Adam Rabung; 23.08.2011
comment
Если ваша анимация поворота находится внутри набора, установка интерполятора не работает. Поворот верхнего элемента исправляет его. - person shalafi; 16.05.2012
comment
Эй, а что, если вы действительно хотите использовать accelerate_decelerate_interpolator без небольшой паузы между каждой анимацией? - person agonist_; 09.06.2016

У меня тоже была эта проблема, и я безуспешно пытался установить линейный интерполятор в xml. Решение, которое сработало для меня, заключалось в создании анимации как RotateAnimation в коде.

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);
person Rabs G    schedule 07.05.2015
comment
если вы хотите, чтобы анимация оставалась в ориентации в конце, добавьте rotate.setFillAfter(true); - person Fonix; 02.07.2018
comment
если вы хотите, чтобы анимация оставалась без конца, добавьтеrotate.setRepeatCount(Animation.INFINITE); - person ahmednabil88; 21.02.2019
comment
если вы хотите, чтобы объект вращался бесконечно, вам нужно установить градус от 180 до 360. Кроме того, требуется setFillAfter, как указано Fonix - person seyedrezafar; 17.11.2020

Это отлично работает

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

Чтобы повернуть назад:

<?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" />
person Luis E. Fernandez    schedule 26.04.2013
comment
Для реверса просто укажите repeatcount 2 и установите android: repeatMode = reverse - нет необходимости иметь два разных файла XML. - person RoundSparrow hilltx; 17.11.2014
comment
почему 358 а не 359 или 360? - person behelit; 21.03.2017
comment
Куда добавить этот файл в ресурсы? Является ли анимация отдельным пакетом для этого? - person abggcv; 09.05.2017

Может, поможет что-то вроде этого:

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

Кстати, можно повернуть более чем на 360, например:

imageView.animate().rotationBy(10000)...
person Vitaly Zinchenko    schedule 07.10.2015
comment
Работает отлично, будет ли imageView.clearAnimation () очищать и запускаемый? - person XcodeNOOB; 13.09.2016
comment
Он отлично работает для запуска анимации, но после запуска он не очищается с помощью imageView.clearAnimation (), я использовал событие сенсорного прослушивателя - person Abhijit Jagtap; 08.03.2017
comment
Невозможно остановить эти исполняемые файлы самостоятельно - person Pants; 02.08.2018
comment
@Pants вы можете вызвать withEndAction (this) при определенных условиях. Если условие ложно, withEndAction (this) не будет вызываться, и анимация должна остановиться - person Vitaly Zinchenko; 02.08.2018

Попробуйте использовать toDegrees="359", поскольку 360 ° и 0 ° одинаковы.

person Fernando Gallego    schedule 17.03.2011

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

Попробуй это.

person Ashraf Rahman    schedule 24.08.2017

Программный объект вращения.

// вращение по часовой стрелке:

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

// Вращение против часовой стрелки:

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

view является объектом вашего ImageView или других виджетов.

rotate.setRepeatCount (10); используйте, чтобы повторить поворот.

500 - продолжительность вашей анимации.

person Geet Thakur    schedule 25.10.2018

Удаление <set>-элемента, заключавшего <rotate>-элемент, решает проблему!

Спасибо Шалафи!

Итак, ваш Rotation_ccw.xml должен выглядеть так:

<?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"
    />
person Christopher Stock    schedule 26.06.2013

В Котлине:

 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)
        })
person Hitesh Sahu    schedule 14.07.2018

Как сказал Ханри выше, установка итерполатора лайнера - это нормально. Но если вращение происходит внутри набора, вы должны указать android: shareInterpolator = "false", чтобы сделать его плавным.

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

Если Sharedinterpolator не является ложным, приведенный выше код дает сбои.

person Rahul Agrawal    schedule 13.01.2015

Независимо от того, что я пробовал, я не мог заставить это работать правильно, используя код (и setRotation) для анимации плавного вращения. В итоге я сделал изменения степени настолько маленькими, что небольшие паузы были незаметны. Если вам не нужно делать слишком много вращений, время на выполнение этого цикла незначительно. Эффект - плавное вращение:

        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);
        }
person ununiform    schedule 25.08.2012
comment
где вы объявление переменной анимации? - person Rishabh Saxena; 17.01.2019

Если вы используете заданную анимацию, как я, вам следует добавить интерполяцию внутри тега 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>

Это сработало для меня.

person Kokusho    schedule 26.05.2015

Попробуйте использовать больше 360, чтобы избежать перезапуска.

Я использую 3600 из 360, и это отлично работает для меня:

<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%" />
person vmtrue    schedule 14.11.2017

Возможно ли, что из-за того, что вы перейдете от 0 к 360, вы потратите немного больше времени на 0/360, чем вы ожидаете? Возможно, установите для градусов 359 или 358.

person William Rose    schedule 28.10.2009
comment
Отличная теория. Я почти уверен, что дело не в этом, потому что ускорение / замедление довольно плавное и продуманное. На всякий случай я попытался уменьшить градусы до 358, но заметных изменений в поведении не было. - person emmby; 28.10.2009

В Android, если вы хотите анимировать объект и заставить его перемещать объект из местоположения 1 в местоположение 2, API анимации определяет промежуточные местоположения (анимация движения), а затем помещает в очередь в основной поток соответствующие операции перемещения в соответствующее время с использованием таймера. . Это прекрасно работает, за исключением того, что основной поток обычно используется для многих других вещей - рисования, открытия файлов, ответа на ввод данных пользователем и т. Д. Таймер в очереди часто может задерживаться. Хорошо написанные программы всегда будут пытаться выполнять как можно больше операций в фоновых (не основных) потоках, однако вы не всегда можете избежать использования основного потока. Операции, требующие от вас работы с объектом пользовательского интерфейса, всегда должны выполняться в основном потоке. Кроме того, многие API-интерфейсы будут перенаправлять операции обратно в основной поток в качестве формы безопасности потоков.

Все представления рисуются в одном потоке графического интерфейса пользователя, который также используется для всего взаимодействия с пользователем.

Поэтому, если вам нужно быстро обновить графический интерфейс или если рендеринг занимает слишком много времени и влияет на взаимодействие с пользователем, используйте SurfaceView.

Пример поворота изображения:

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

Мероприятия:

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));
    }
}
person phnmnn    schedule 19.05.2015

person    schedule
comment
Хотя этот код может предоставить решение вопроса, лучше добавить контекст, объясняющий, почему и как он работает. Это может помочь будущим пользователям учиться и применять эти знания в собственном коде. Вы также, вероятно, получите положительные отзывы от пользователей в виде голосов за, когда объясните код. - person borchvm; 10.03.2020