Как сделать плавное вращение изображения в 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
), но я не знаю, как сказать ему, чтобы не интерполировать анимацию.
Как отключить интерполяцию (если это действительно проблема), чтобы сделать анимационный цикл плавно?
Ответы
Ответ 1
Вы правы в 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"
Специальное примечание: Если ваша анимация вращения находится внутри набора, настройка интерполятора не работает. Поворачивая его, верхний элемент фиксирует его. (это сэкономит ваше время.)
Ответ 2
У меня была и эта проблема, и я попытался установить линейный интерполятор в 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);
Ответ 3
Это прекрасно работает
<?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" />
Ответ 4
Попробуйте использовать toDegrees="359"
, так как 360 ° и 0 ° совпадают.
Ответ 5
Возможно, что-то вроде этого поможет:
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)...
Ответ 6
Обрезка <set>
-Element, который завернул элемент <rotate>
-Element, решает проблему!
Благодаря Шалафи!
Итак, ваш 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"
/>
Ответ 7
ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).setDuration(300).start();
Попробуй это.
Ответ 8
Независимо от того, что я пробовал, я не мог заставить это работать правильно, используя код (и 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);
}
Ответ 9
Как упоминалось выше, hanry прикладывает вкладыш 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 не является ложным, приведенный выше код дает глюки.
Ответ 10
Если вы используете набор анимаций, подобных мне, вы должны добавить интерполяцию внутри установленного тега:
<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>
Это работало для меня.
Ответ 11
Вращение объекта программно.
//вращение по часовой стрелке:
public void rorate_Clockwise(View view) {
ObjectAnimator rotate = ObjectAnimator.ofFloat(view, "rotation", 180f, 0f);
// rotate.setRepeatCount(10);
rotate.setDuration(500);
rotate.start();
}
//вращение против часовой стрелки:
public void rorate_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 - ваша продолжительность анимации.
Ответ 12
Возможно ли, что, поскольку вы идете от 0 до 360, вы тратите немного больше времени на 0/360, чем вы ожидаете? Возможно, установите значениеDegrees на 359 или 358.
Ответ 13
В Android, если вы хотите анимировать объект и заставить его перемещать объект из location1 в location2, API-интерфейс анимации вычисляет промежуточные местоположения (tweening), а затем ставит в очередь на основной поток соответствующие операции перемещения в соответствующие моменты времени используя таймер. Это прекрасно работает, за исключением того, что основной поток обычно используется для многих других вещей - рисования, открытия файлов, реагирования на пользовательские входы и т.д. Таймер в очереди может часто задерживаться. Хорошо написанные программы всегда будут стараться делать как можно больше операций в фоновом (не главном) потоке, но вы всегда можете избежать использования основного потока. Операции, требующие работы с объектом пользовательского интерфейса, всегда должны выполняться в основном потоке. Кроме того, многие 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));
}
}
Ответ 14
Попробуйте использовать более 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%" />
Ответ 15
В Котлине:
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)
})