我有一个自定义按钮,这是我自己的子类UIButton
。 它看起来像一个圆圈的箭头,开始以某一角度startAngle
在一些最终精加工endAngle=startAngle+1.5*M_PI
。 startAngle
是一个按钮的属性,然后在其使用drawRect:
方法。 我想使这个箭头当按下按钮继续由二皮围绕其中心旋转。 所以我想,我可以很容易地使用[UIView beginAnimations: context:]
但显然它不能使用,因为它不允许动画自定义属性。 CoreAnimation也不适合,因为它只是动画的CALayer
属性。
那么,什么是实现的一个自定义属性的动画最简单的方法UIView
子类中的iOS? 或者,也许我错过了什么,并有可能与已经提到的技术?
谢谢。
由于Jenox我已经更新使用CADisplayLink这似乎比真正的NSTimer更正确的解决方案动画代码。 所以,我现在可以显示正确实施与CADisplayLink。 这是非常接近前面的一个,但即使是简单一点。
我们的QuartzCore框架添加到我们的项目中。 然后,我们把下面的线是我们班的头文件:
CADisplayLink* timer;
Float32 animationDuration; // Duration of one animation loop
Float32 initAngle; // Represents the initial angle
Float32 angle; // Angle used for drawing
CFTimeInterval startFrame; // Timestamp of the animation start
-(void)startAnimation; // Method to start the animation
-(void)animate; // Method for updating the property or stopping the animation
现在,在实现文件中,我们为动画和其他初始值的持续时间设置的值:
initAngle=0.75*M_PI;
angle=initAngle;
animationDuration=1.5f; // Arrow makes full 360° in 1.5 seconds
startFrame=0; // The animation hasn't been played yet
要启动,我们需要创建CADisplayLink实例,它会调用方法动画animate
,并把它添加到我们的应用程序的主要RunLoop:
-(void)startAnimation
{
timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(animate)];
[timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
这个计时器将调用animate
方法的应用程序的每个runLoop。
所以,现在我们来更新每个循环后的财产法的实施:
-(void)animate
{
if(startFrame==0) {
startFrame=timer.timestamp; // Setting timestamp of start of animation to current moment
return; // Exiting till the next run loop
}
CFTimeInterval elapsedTime = timer.timestamp-startFrame; // Time that has elapsed from start of animation
Float32 timeProgress = elapsedTime/animationDuration; // Determine the fraction of full animation which should be shown
Float32 animProgress = timingFunction(timeProgress); // 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 (timeProgress>=1.f)
{ // Stopping animation
angle=initAngle; // Setting angle to initial value to exclude rounding effects
[timer invalidate]; // Stopping the timer
startFrame=0; // Resetting time of start of animation
}
[self setNeedsDisplay]; // Redrawing with updated angle value
}
所以不像情况下用的NSTimer我们现在并不需要计算的时间间隔要更新的角度财产和重绘按钮。 现在,我们只需要算多少时间已经从动画开始传递和属性设置为对应于这种进步的价值。
我必须承认,动画作品有点更加顺畅比的NSTimer的情况。 默认情况下,CADisplayLink调用animate
方法每次运行循环。 当我所计算的帧速率,它是120帧。 我想,这是不是很有效,所以我已经帧速率通过改变CADisplayLink的frameInterval属性将它添加到mainRunLoop之前下降到只有22 FPS:
timer.frameInterval=3;
这意味着,它会调用animate
方法,在第一次运行循环,然后什么也不做未来3圈,并在4次呼叫,等等。 这就是为什么frameInterval只能是整数。
再次感谢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或其它默认animationCurves适用于这种手工动画。 我认为它不存在。 但是,当然,可以通过手来做到这一点。 静置该定时函数的行是
Float32 animProgress = nFrames/(animationDuration*animationFrameRate);
它可以被看作是Float32 y = x;
,这意味着线性特性,以恒定的速度,这是相同的,作为时间的速度。 但是,你可以修改它要像y = cos(x)
或y = sqrt(x)
或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的工具绘图器有很大帮助的可视化功能,让你可以看到你的动画的进步将如何依赖于时间进度。
希望它可以帮助别人...
- (void)onImageAction:(id)sender
{
UIButton *iconButton = (UIButton *)sender;
if(isExpand)
{
isExpand = FALSE;
// With Concurrent Block Programming:
[UIView animateWithDuration:0.4 animations:^{
[iconButton setFrame:[[btnFrameList objectAtIndex:iconButton.tag] CGRectValue]];
} completion: ^(BOOL finished) {
[self animationDidStop:@"Expand" finished:YES context:nil];
}];
}
else
{
isExpand = TRUE;
[UIView animateWithDuration:0.4 animations:^{
[iconButton setFrame:CGRectMake(30,02, 225, 205)];
}];
for(UIButton *button in [viewThumb subviews])
{
[button setUserInteractionEnabled:FALSE];
//[button setHidden:TRUE];
}
[viewThumb bringSubviewToFront:iconButton];
[iconButton setUserInteractionEnabled:TRUE];
// [iconButton setHidden:FALSE];
}
}