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

217

Я использую 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), но я не знаю, как сказать ему не интерполировать анимацию.

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

emmby
источник

Ответы:

199

Вы правы насчет 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"

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

Бахтиер
источник
1
Это потому, что интерполятор был написан неправильно (без "n"). Вам не нужно делать свой собственный
Kingpin
25
Я пробовал каждый доступный интерполятор, включая линейный, и я все еще получаю эту маленькую «заминку» в начале каждого цикла.
Адам Рабунг
11
Если ваша анимация поворота находится внутри набора, настройка интерполятора не работает. Делая поворот верхнего элемента исправляет это
shalafi
Эй, что, если вы действительно хотите использовать accelerate_decelerate_interpolator без небольшой «паузы» между каждой анимацией?
agonist_
90

У меня тоже была эта проблема, и я безуспешно пытался установить линейный интерполятор в 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);
Rabs G
источник
9
если вы хотите, чтобы анимация оставалась в конце в ориентации, добавьтеrotate.setFillAfter(true);
Fonix
4
если вы хотите, чтобы анимация rotate.setRepeatCount(Animation.INFINITE);
оставалась
38

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

<?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" />
Луис Э. Фернандес
источник
5
Для обратного достаточно просто повторить счет 2 и установить android: repeatMode = "reverse" - не нужно иметь два разных XML-файла.
RoundSparrow Hilltx
3
почему 358 а не 359 или 360?
behelit
Куда добавить этот файл в ресурсы? Является ли анимация отдельным пакетом для этого?
abggcv
30

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

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)...
Виталий Зинченко
источник
Отлично работает, imageView.clearAnimation () также очистит работоспособный объект?
XcodeNOOB
Он отлично работает для запуска анимации, но после запуска запуска не очищается с помощью imageView.clearAnimation (), я использовал в событии слушателя касания
Abhijit Jagtap
Не могу остановить эти runnables независимо друг от друга
Штаны
@ Штаны, которые вы можете вызывать с помощью EndAction (this) при некоторых условиях. Если условие ложно, withEndAction (this) не будет вызываться, и анимация должна прекратиться
Виталий Зинченко,
30

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

Фернандо Гальего
источник
19
ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).setDuration(300).start();

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

Ашраф Рахман
источник
8

Вращение объекта программно.

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

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

Посмотреть - это объект вашего ImageView или других виджетов.

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

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

Geet Thakur
источник
6

Обрезка <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"
    />
Кристофер Сток
источник
3

Независимо от того, что я пытался, я не мог заставить это работать правильно, используя код (и 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);
        }
неравномерным
источник
где у вас "анимация" объявления переменных?
Ришабх Саксена
3

Как уже упоминал Ханри, подкладка лайнера iterpolator вполне подойдет. Но если вращение находится внутри набора, вы должны поставить 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 не равен false, приведенный выше код дает глюки.

Рахул Агравал
источник
3

Если вы используете набор анимации, как я, вы должны добавить интерполяцию внутри тега набора:

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

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

Kokusho
источник
3

В Котлине:

 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)
        })
Хите саху
источник
1

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

Уильям Роуз
источник
1
Отличная теория. Я уверен, что это не так, потому что ускорение / замедление довольно плавное и продуманное. На всякий случай, хотя я пытался уменьшить градусы до 358, и не было заметных изменений в поведении.
Эмби
1

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

Я использую 3600 insted из 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%" />
vmtrue
источник
0

В Android, если вы хотите анимировать объект и заставить его переместить объект из location1 в location2, 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));
    }
}
phnmnn
источник
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()
}
ТОУСИФ АХАММАД
источник
1
Хотя этот код может обеспечить решение вопроса, лучше добавить контекст относительно того, почему / как он работает. Это может помочь будущим пользователям учиться и применять эти знания в своем собственном коде. Вы также, вероятно, получите положительные отзывы от пользователей в виде откликов, когда код объясняется.
Borchvm