Как сделать плавный поворот изображения в 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
), но я не знаю, как сказать ему, чтобы он не интерполировал анимацию.
Как я могу отключить интерполяцию (если это действительно проблема), чтобы сделать мой цикл анимации плавным?
Решение
Вы правы насчет 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"
Специальное примечание . Если анимация поворота находится внутри набора, настройка интерполятора не работает. Поворот верхнего элемента исправляет это. (это сэкономит ваше время.)
Другие советы
У меня тоже была эта проблема, и я безуспешно пытался установить линейный интерполятор в 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);
Это отлично работает
<?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" />
Может быть, что-то вроде этого поможет:
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)...
Попробуйте использовать toDegrees="359"
начиная с 360 & # 176; и 0 & # 176; одинаковы.
Удаление элемента <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"
/>
ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).setDuration(300).start();
Попробуйте это.
Независимо от того, что я пытался, я не мог заставить это работать правильно, используя код (и 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);
}
Как уже упоминал Ханри, наносить лайнер итерполятор можно. Но если вращение находится внутри набора, вы должны поставить android: shareInterpolator = & Quot; false & Quot; чтобы сделать его гладким.
<?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, приведенный выше код дает сбои.
Поворот объекта программно.
// вращение по часовой стрелке:
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 - это продолжительность анимации.
Если вы используете анимацию набора, как я, вы должны добавить интерполяцию внутри тега набора:
<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>
Это сработало для меня. Р>
Возможно ли, что с 0 до 360 вы потратите немного больше времени на 0/360, чем ожидаете? Возможно, установите в градусы 359 или 358.
В 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));
}
}
Попробуйте использовать больше 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%" />
В Котлине:
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)
})