iOS中非常定制的UIButton动画
我有一个自定义按钮,它是我自己的iOS中非常定制的UIButton动画,ios,animation,uibutton,Ios,Animation,Uibutton,我有一个自定义按钮,它是我自己的UIButton的子类。它看起来像一个圆圈箭头,从某个角度开始startAngle结束于某个角度endAngle=startAngle+1.5*M_PI。 startAngle是按钮的属性,然后在其drawRect:方法中使用。 我想让这个箭头在按下这个按钮时,围绕它的中心连续旋转2Pi。因此,我认为我可以很容易地使用[UIView beginAnimations:context://code>,但显然不能使用它,因为它不允许设置自定义属性的动画。CoreAnim
UIButton
的子类。它看起来像一个圆圈箭头,从某个角度开始startAngle
结束于某个角度endAngle=startAngle+1.5*M_PI
。
startAngle
是按钮的属性,然后在其drawRect:
方法中使用。
我想让这个箭头在按下这个按钮时,围绕它的中心连续旋转2Pi。因此,我认为我可以很容易地使用[UIView beginAnimations:context://code>,但显然不能使用它,因为它不允许设置自定义属性的动画。CoreAnimation也不适合,因为它只为CALayer
属性设置动画
那么,在iOS中实现UIView
子类的自定义属性动画的最简单方法是什么?
或者也许我错过了一些东西,这是可能的与已经提到的技术
谢谢。再次感谢您建议使用计时器。我对使用NSTimer做了一些研究,现在我想展示我的实现,因为我认为它对其他人可能有用
所以在我的例子中,我有一个UIButton子类,它画了一个从某个角度开始的弯曲箭头Float32代码>这是整个箭头的绘图开始的主要属性。这意味着只需更改角度值即可旋转整个箭头。因此,为了制作这个旋转的动画,我在类的头文件中放了以下几行:
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实例,该实例将每隔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秒重复一次,直到手动停止
现在,我们实现了一个步骤的动画制作方法:
-(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
}
它可以被视为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圈,然后旋转回初始角度,这意味着您的动画进度将从0变为1.5,然后再变回1,而时间进度将从0变为1。)
为了安全起见,我们现在将时间进度和动画进度分开:
Float32 timeProgress = nFrames/(animationDuration*animationFrameRate);
Float32 animProgress = animationCurve(timeProgress);
然后检查时间进度以确定动画是否应停止:
if(timeProgress>=1.f)
{
// Stop the animation
}
顺便说一下,如果有人知道一些有用的动画计时函数列表的来源,如果你能与我分享,我将不胜感激
内置MacOS X实用程序图示器在可视化功能方面有很大帮助,因此您可以看到动画进度如何依赖于时间进度
希望它能帮助一些人…多亏了我使用CADisplayLink更新了动画代码,这似乎比Ntimer更正确。现在,我展示了使用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
,并将其添加到应用程序的主运行循环中:
-(void)startAnimation
{
timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(animate)];
[timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
此计时器将在应用程序的每个运行循环中调用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的情况不同,我们现在不需要计算更新角度属性和重新绘制按钮的时间间隔。现在,我们只需要计算从动画开始经过的时间,并将属性设置为与此进度相对应的值
我必须承认,动画比NSTimer更流畅。
默认情况下,CADisplayLink在每次运行循环时调用animate
方法。当我计算帧速率时,它是120 fps。我认为这不是很有效,因此在将其添加到mainRunLoop之前,我通过更改CADisplayLink的frameInterval属性将帧速率降低到22 fps:
timer.frameInterval=3;
这意味着它将在第一次运行循环时调用animate
方法,然后在接下来的3个循环中不执行任何操作,并在第4个循环中调用,依此类推。这就是为什么frameInterval只能是整数。只需将旋转添加到视图中即可。你不需要重画。。。我是说你的按钮的视图。您的CustomButton是UIButton的子类,witch是UIView的子类。谢谢。在这种特殊情况下,它是可以接受的,但它不是通用的。我还有另一个按钮,旋转的部分独立于按钮的其他元素,因此我需要精确地设置属性的动画。您可以使用计时器:[NST]
- (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];
}
}
- (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];
}
}