iOS中非常自定义的UIButtonanimation

我有一个自定义button,这是我自己的UIButton的子类。 它看起来像一个圆圈箭头,从某个angular度startAngle结束在某个endAngle=startAngle+1.5*M_PIstartAngle是一个button的属性,然后在其drawRect:方法中使用。 当按下这个button时,我想让这个箭头在它的中心附近连续旋转2Pi。 所以我认为我可以很容易地使用[UIView beginAnimations: context:]但显然它不能被使用,因为它不允许animation定制属性。 CoreAnimation也没有套件,因为它只能animationCALayer属性。

那么在iOS中实现UIView子类的自定义属性的animation最简单的方法是什么? 或者,也许我错过了什么,这是可能的已经提到的技术?

谢谢。

感谢Jenox,我用CADisplayLink更新了animation代码,这似乎比NSTimer更真实的解决scheme。 所以我现在用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 

现在在实现文件中,我们设置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 

要开始animation,我们需要创build一个将调用方法animate的CADisplayLink实例,并将其添加到我们的应用程序的主RunLoop中:

 -(void)startAnimation { timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(animate)]; [timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; } 

这个计时器会调用应用程序的每个runLoop的animate方法。

所以现在来实现我们的方法来更新每个循环后的属性:

 -(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不同,我们现在不需要计算更新angular度属性的时间间隔,并重新绘制button。 我们现在只需要计算从animation开始经过多less时间,并将属性设置为与此进度对应的值。

而且我必须承认,animation比NSTimer更平滑。 默认情况下,CADisplayLink调用每个运行循环的animate方法。 当我计算帧率时,是120 fps。 我认为这不是非常高效,所以我通过改变CADisplayLink的frameInterval属性将帧速率降低到22fps,然后将它添加到mainRunLoop:

 timer.frameInterval=3; 

这意味着它将在第一次运行循环中调用animate方法,然后不做任何下一个3循环,然后调用第4个循环,以此类推。 这就是为什么frameInterval只能是整数。

再次感谢k06abuild议使用定时器。 我已经做了一些关于NSTimer的研究,现在我想展示一下我的实现,因为我认为它对其他人有用。

所以在我的情况下,我有一个UIButton子类正在绘制一个弯曲的箭头,从某个angular度Float32 angle; 这是整个箭头开始绘制的主要属性。 这意味着只要改变angular度值就会旋转整个箭头。 所以为了使这个旋转的animation,我把我的类的头文件中的以下行:

 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 

现在在实现文件中,我设置了我的animation的持续时间和帧速率的值以及绘图的初始angular度:

 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 

要开始animation,我们需要创buildNSTimer实例,它将每1/15秒调用一次方法animate:(NSTimer*) timer

 -(void)startAnimation { timer = [NSTimer scheduledTimerWithTimeInterval:1.f/animationFrameRate target:self selector:@selector(animate:) userInfo:nil repeats:YES]; } 

这个计时器将立即调用animate:方法,然后每1/15秒重复一次,直到手动停止。

所以现在来实现我们的animation单步的方法:

 -(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没有工作,由于四舍五入效应。 完整轮换之后它们不完全相同。 这就是为什么我检查它们是否足够接近,然后将angular度值设置为初始值以阻止许多重复animation循环之后的angular度值的小漂移。 为了完全正确,这个代码还必须将angular度转换为始终在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或者其他的默认animation曲线来进行这种手动animation。 我认为它不存在。 但是,当然可以用手去做。 该定时function的线是

 Float32 animProgress = nFrames/(animationDuration*animationFrameRate); 

它可以被当作Float32 y = x; 这意味着线性行为,一个恒定的速度,这是一样的时间的速度。 但是你可以修改它,就像y = cos(x)或者y = sqrt(x)或者y = pow(x,3.f)会产生一些非线性行为。 考虑到x将从0(animation开始)到1(animation结束),你可以自己想一想。

为了更好的看代码,最好做一些独立的计时function:

 Float32 animationCurve(Float32 x) { return sin(x*0.5*M_PI); } 

但是现在,由于animation进度与时间的依赖关系并不是线性的,所以以时间为指标来停止animation是比较安全的。 (例如,您可能需要使箭头变成1.5圈,然后旋转回初始angular度,这意味着您的animProgress将从0变为1.5,然后返回到1,而timeProgress将从0变为1.)为了安全起见,我们将时间进度和animation进度分开:

 Float32 timeProgress = nFrames/(animationDuration*animationFrameRate); Float32 animProgress = animationCurve(timeProgress); 

然后检查时间进度来决定是否应该停止animation:

 if(timeProgress>=1.f) { // Stop the animation } 

顺便说一下,如果有人知道一些有用的animation定时function的来源,如果你分享它们,我将不胜感激。

内置的MacOS X实用程序Grapher可以帮助您在视觉化function方面提供很多帮助,以便您了解animation进度将取决于时间进度。

希望它有助于某人…

 - (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]; } }