再次感谢k06a建议使用计时器。我已经对使用 NSTimer 进行了一些研究,现在我想展示我的实现,因为我认为它对其他人有用。
所以在我的例子中,我有一个 UIButton 子类,它正在绘制一个从某个角度开始的弯曲箭头,Float32 angle;
这是整个箭头绘制开始的主要属性。这意味着只需更改角度的值就会旋转整个箭头。所以为了制作这个旋转的动画,我在我的类的头文件中加入了以下几行:
NSTimer* timer;
Float32 animationDuration; // Duration of one animation loop
Float32 animationFrameRate; // Frames per second
Float32 initAngle; // Represents the initial angle
Float32 angle; // Angle used for drawing
UInt8 nFrames; // Number of played frames
-(void)startAnimation; // Method to start the animation
-(void)animate:(NSTimer*) timer; // Method for drawing one animation step and stopping the animation
现在在实现文件中,我设置动画的持续时间和帧速率以及绘制的初始角度:
initAngle=0.75*M_PI;
angle=initAngle;
animationDuration=1.5f; // Arrow makes full 360° in 1.5 seconds
animationFrameRate=15.f; // Frame rate will be 15 frames per second
nFrames=0; // The animation hasn't been played yet
要开始动画,我们需要创建 NSTimer 实例,该实例将animate:(NSTimer*) timer
每 1/15 秒调用一次方法:
-(void)startAnimation
{
timer = [NSTimer scheduledTimerWithTimeInterval:1.f/animationFrameRate target:self selector:@selector(animate:) userInfo:nil repeats:YES];
}
此计时器将animate:
立即调用方法,然后每 1/15 秒重复一次,直到手动停止。
所以现在来实现我们为单个步骤设置动画的方法:
-(void)animate:(NSTimer *)timer
{
nFrames++; // Incrementing number of played frames
Float32 animProgress = nFrames/(animationDuration*animationFrameRate); // The current progress of animation
angle=initAngle+animProgress*2.f*M_PI; // Setting angle to new value with added rotation corresponding to current animation progress
if (animProgress>=1.f)
{ // Stopping animation when progress is >= 1
angle=initAngle; // Setting angle to initial value to exclude rounding effects
[timer invalidate]; // Stopping the timer
nFrames=0; // Resetting counter of played frames for being able to play animation again
}
[self setNeedsDisplay]; // Redrawing with updated angle value
}
我要提到的第一件事是,对我来说,angle==initAngle
由于舍入效应,比较线不起作用。完全旋转后它们并不完全相同。这就是为什么我检查它们是否足够接近,然后将角度值设置为初始值,以在多次重复动画循环后阻止角度值的小漂移。为了完全正确,这段代码还必须管理角度的转换,使其始终介于 0 和 2*M_PI 之间,如下所示:
angle=normalizedAngle(initAngle+animProgress*2.f*M_PI);
在哪里
Float32 normalizedAngle(Float32 angle)
{
while(angle>2.f*M_PI) angle-=2.f*M_PI;
while(angle<0.f) angle+=2.f*M_PI;
return angle
}
另一个重要的事情是,不幸的是,我不知道任何简单的方法可以将easeIn、easeOut 或其他默认动画曲线应用于这种手动动画。我认为它不存在。但是,当然,可以手动完成。代表该计时功能的线是
Float32 animProgress = nFrames/(animationDuration*animationFrameRate);
它可以被视为Float32 y = x;
,即线性行为,恒定速度,与时间速度相同。但是您可以将其修改为y = cos(x)
or y = sqrt(x)
or y = pow(x,3.f)
,这将产生一些非线性行为。考虑到 x 将从 0(动画开始)变为 1(动画结束),您可以自己考虑。
为了更好看的代码,最好做一些独立的计时功能:
Float32 animationCurve(Float32 x)
{
return sin(x*0.5*M_PI);
}
但是现在,由于动画进度和时间之间的依赖关系不是线性的,所以使用时间作为停止动画的指标会更安全。(例如,您可能希望使箭头旋转 1.5 圈,然后旋转回初始角度,这意味着您的 animProgress 将从 0 变为 1.5,而不是返回 1,而 timeProgress 将从 0 变为 1。)所以为了安全起见,我们现在将时间进度和动画进度分开:
Float32 timeProgress = nFrames/(animationDuration*animationFrameRate);
Float32 animProgress = animationCurve(timeProgress);
然后检查时间进度以确定动画是否应该停止:
if(timeProgress>=1.f)
{
// Stop the animation
}
顺便说一句,如果有人知道一些具有有用的动画计时功能列表的资源,如果您分享它们,我将不胜感激。
内置 MacOS X 实用程序 Grapher 在可视化功能方面有很大帮助,因此您可以看到动画进度将如何取决于时间进度。
希望它可以帮助某人...