如何在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" />

当我使用AndroidUtils.loadAnimation()将此应用到我的ImageView时,它工作得很好!

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

唯一的问题是,图像旋转似乎在每个周期的顶部暂停。

换句话说,图像旋转360度,短暂停顿,然后再次旋转360度,等等。

我怀疑问题是动画正在使用像android:iterpolator="@android:anim/accelerate_interpolator" (AccelerateInterpolator)这样的默认插补器,但我不知道如何告诉它不要插补动画。

我怎么能关闭插值(如果这确实是问题),使我的动画周期顺利?

275545 次浏览

有没有可能因为你从0到360,你在0/360处花的时间比你预期的要多一点?可能设置为359度或358度。

你是正确的关于AccelerateInterpolator;你应该使用线性插值器代替。

你可以使用内置的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"

特别注意:如果你的旋转动画在一个集合内,设置插值器似乎不工作。将旋转设置为顶部元素可以修复它。(这将节省你的时间。)

尝试使用toDegrees="359",因为360°和0°是相同的。

无论我怎么尝试,我都无法使用代码(和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);
}

这很好

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

修剪包装<rotate>-Element的<set>-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"
/>

正如hanry上面提到的,放置线性迭代器是可以的。但如果旋转是在一个集合内,你必须放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,上述代码会出现故障。

我也有这个问题,并试图在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);

在Android中,如果你想动画一个对象并让它从location1移动到location2,动画API会计算出中间位置(补间),然后使用计时器在适当的时间将适当的移动操作排队到主线程中。这很好,除了主线程通常用于许多其他事情-绘画,打开文件,响应用户输入等。排队计时器经常会延迟。编写良好的程序总是尝试在后台(非主线程)执行尽可能多的操作,但是你不能总是避免使用主线程。需要在UI对象上操作的操作总是必须在主线程上完成。此外,许多api将操作漏斗回主线程作为线程安全的一种形式。

视图都绘制在相同的GUI线程上,该线程也用于所有用户交互。

所以,如果你需要快速更新GUI,或者渲染花费太多时间,影响用户体验,那么就使用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));
}
}

如果你像我一样使用set Animation,你应该在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>

这对我很管用。

也许,这样做会有帮助:

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)...
ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).setDuration(300).start();

试试这个。

尝试使用超过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%" />

在芬兰湾的科特林:

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

以编程方式旋转对象。

//顺时针旋转:

    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是你的动画时长。

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

以下是对我来说工作良好的代码片段:

    RotateAnimation rotate = new RotateAnimation(
0, 359,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f
);


rotate.setDuration(1500);
rotate.setRepeatCount(Animation.INFINITE);
yourView.startAnimation(rotate);

检查一下,它不是360,而是构造函数中的359,因为0和360在同一点。

你可以使用下面的代码:

view.animate().rotation(360.0f).setDuration(1000);

100%正确的答案

    var rotate = RotateAnimation(
0F, 360F,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f
)


rotate.duration = 999
rotate.repeatCount = Animation.INFINITE


imageview..setAnimation(rotate)

100%的工作! !