|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
管理所有设备发生的事件比如屏幕旋转屏幕关闭或者一些其他的程序的控制逻辑也应该写在这里他的初始化函数是-(id)initWithNibName:(NSString*)nibNamebundle:(NSBundle*)nibBundle后面那个NibName是InterfaceBuilder里设计的界面现在IB已经集成到XCode里了默许情形下,CALayer及其子类的尽年夜部分尺度属性都能够实行动画,不管是增加一个CAAnimation到Layer(显式动画),亦或是为属性指定一个举措然后修正它(隐式动画)。
但偶然候我们但愿能同时为好几个属性增加动画,使它们看起来像是一个动画一样;大概,我们必要实行的动画不克不及经由过程利用尺度Layer属性动画来完成。
在本文中,我们将会商怎样子类化CALayer并增加我们本人的属性,以便对照简单地创立那些假如以其他体例完成起来会很贫苦的动画效果。
一样平常说来,我们但愿增加到CALayer的子类上的可动画属性有三品种型:
- 能直接动画Layer(或其子类)的一个或多个尺度属性的属性。
- 能触发Layer面前的图象(即contents属性)重绘的属性。
- 不触及Layer重绘或对任何已有属性实行动画的属性。
直接属性动画
能直接修正别的尺度Layer属性的自界说属性是这些选项中最复杂的。它们仅仅只是自界说setter办法。然后将它们的输出转换为合用于创立动画的一个或多个分歧的值。
假如被我们设置的属性已预设好尺度动画,那我们完整不必要编写任何实践的动画代码,由于我们修正这些属性后,它们就会承继任何被设置在以后CATransaction上的动画设置,而且主动实行动画。
换句话说,即便CALayer不晓得怎样对我们自界说的属性举行动画,它仍然能对因自界说属性被改动而引发的别的可见反作用举行动画,而这刚好就是我们所必要的。
为了演示这类办法,让我们来创立一个复杂的摹拟时钟,以后我们可使用被声明为NSDate范例time属性来设置它的工夫。我会将从创立一个静态的时钟面盘入手下手。这个时钟包括三个CAShapeLayer实例——一个用于时钟面盘的圆形Layer和两个用于时针和分针的长方形Sublayer。- @interfaceClockFace:CAShapeLayer@property(nonatomic,strong)NSDate*time;@end@interfaceClockFace()//公有属性@property(nonatomic,strong)CAShapeLayer*hourHand;@property(nonatomic,strong)CAShapeLayer*minuteHand;@end@implementationClockFace-(id)init{if((self=[superinit])){self.bounds=CGRectMake(0,0,200,200);self.path=[UIBezierPathbezierPathWithOvalInRect:self.bounds].CGPath;self.fillColor=[UIColorwhiteColor].CGColor;self.strokeColor=[UIColorblackColor].CGColor;self.lineWidth=4;self.hourHand=[CAShapeLayerlayer];self.hourHand.path=[UIBezierPathbezierPathWithRect:CGRectMake(-2,-70,4,70)].CGPath;self.hourHand.fillColor=[UIColorblackColor].CGColor;self.hourHand.position=CGPointMake(self.bounds.size.width/2,self.bounds.size.height/2);[selfaddSublayer:self.hourHand];self.minuteHand=[CAShapeLayerlayer];self.minuteHand.path=[UIBezierPathbezierPathWithRect:CGRectMake(-1,-90,2,90)].CGPath;self.minuteHand.fillColor=[UIColorblackColor].CGColor;self.minuteHand.position=CGPointMake(self.bounds.size.width/2,self.bounds.size.height/2);[selfaddSublayer:self.minuteHand];}returnself;}@end
复制代码 同时我们要设置一个包括UIDatePicker的基础的ViewController,如许我们就可以测试我们的Layer(日期选择器在Storyboard里设置)了:- @interfaceViewController()@property(nonatomic,strong)IBOutletUIDatePicker*datePicker;@property(nonatomic,strong)ClockFace*clockFace;@end@implementationViewController-(void)viewDidLoad{[superviewDidLoad];//增加时钟面板Layerself.clockFace=[[ClockFacealloc]init];self.clockFace.position=CGPointMake(self.view.bounds.size.width/2,150);[self.view.layeraddSublayer:self.clockFace];//设置默许工夫self.clockFace.time=[NSDatedate];}-(IBAction)setTime{self.clockFace.time=self.datePicker.date;}@end
复制代码 如今我们只必要完成time属性的setter办法。这个办法利用NSCalendar将工夫变成小时和分钟,以后我们将它们转换为角坐标。然后我们就能够利用这些角度往天生两个CGAffineTransform以扭转时针和分针。- -(void)setTime:(NSDate*)time{_time=time;NSCalendar*calendar=[[NSCalendaralloc]initWithCalendarIdentifier:NSGregorianCalendar];NSDateComponents*components=[calendarcomponents:NSHourCalendarUnit|NSMinuteCalendarUnitfromDate:time];self.hourHand.affineTransform=CGAffineTransformMakeRotation(components.hour/12.0*2.0*M_PI);self.minuteHand.affineTransform=CGAffineTransformMakeRotation(components.minute/60.0*2.0*M_PI);}
复制代码 了局看起来像如许:
<br>
你能够从GitHub高低载这个项目看看。
如你所见,我们其实没有做甚么太费头脑的事变;我们并没有创立一个新的可动画属性,而只是在单个办法里设置了几个尺度可动画Layer属性罢了。但是,假如我们想创立的动画其实不能映照就任何已有的Layer属性上时,该怎样办呢?
动画Layer内容
假定不利用几个分别的Layer来完成我们的时钟面板,那我们能够改用CoreGraphics来绘制时钟。(这一般会下降功能,但我们能够设想我们所要完成的效果必要很多庞大的画图操纵,而它们很难用惯例的Layer属性和transform来复制。)我们要怎样做呢?
与NSManagedObject很相似,CALayer具无为任何被声明的属性天生dynamic的setter和getter的才能。在我们以后的完成中,我们让编译器往synthesize了time属性的ivar和getter办法,而我们本人完成了setter办法。但让我们来改动一下:抛弃我们的setter并将属性标志为@dynamic。同时我们也抛弃分别的时针和分针Layer,由于我们将本人往绘制它们。- @interfaceClockFace()@end@implementationClockFace@dynamictime;-(id)init{if((self=[superinit])){self.bounds=CGRectMake(0,0,200,200);}returnself;}@end
复制代码 在我们入手下手之前,必要先做一个小调剂:由于不幸的是,CALayer不晓得怎样对NSDate属性举行插值(interpolate)(比方,固然它能够处置数字范例和别的比方CGColor和CGAffineTransform如许的范例,但它不克不及主动天生分歧的NSDate实例之间的两头值)。我们能够保存我们的自界说setter办法并用它设置另外一个等价于NSTimeInterval的静态属性(这是一个数字值,能够被插值),但为了坚持例子的复杂性,我们会用一个浮点值交换NSDate属性来表征时钟的小时。我们还更新了用户界面,如今利用一个复杂的UITextField来设置浮点值,而不再利用日期选择器:- @interfaceViewController()<UITextFieldDelegate>@property(nonatomic,strong)IBOutletUITextField*textField;@property(nonatomic,strong)ClockFace*clockFace;@end@implementationViewController-(void)viewDidLoad{[superviewDidLoad];//增加时钟面板Layerself.clockFace=[[ClockFacealloc]init];self.clockFace.position=CGPointMake(self.view.bounds.size.width/2,150);[self.view.layeraddSublayer:self.clockFace];}-(BOOL)textFieldShouldReturn:(UITextField*)textField{[textFieldresignFirstResponder];returnYES;}-(void)textFieldDidEndEditing:(UITextField*)textField{self.clockFace.time=[textField.textfloatValue];}@end
复制代码 如今,既然我们已移除自界说的setter办法,那我们要怎样才干晓得time属性的改动呢?我们必要一个不管什么时候time属性改动时都能主动关照CALayer的体例,如许它才好重绘它的内容。我们经由过程覆写+needsDisplayForKey:办法便可做到这一点,以下:- +(BOOL)needsDisplayForKey:(NSString*)key{if([@"time"isEqualToString:key]){returnYES;}return[superneedsDisplayForKey:key];}
复制代码 这就告知了Layer,不管什么时候time属性被修正,它都必要挪用-display办法。如今我们就覆写-display办法,增加一个NSLog语句打印出time的值:- -(void)display{NSLog(@"time:%f",self.time);}
复制代码 假如我们设置time属性为1.5,我们就会看到-display被挪用,打印出新值:- 2014-04-2822:37:04.253ClockFace[49145:60b]time:1.500000
复制代码 但这还不是我们真正想要的;我们但愿time属功能在旧值和新值之间在几帧以内做一个光滑的过渡动画。为了完成这一点,我们必要为time属性指定一个动画(或“举措(action)”),而经由过程覆写-actionForKey:办法就可以做到:- -(id<CAAction>)actionForKey:(NSString*)key{if([keyisEqualToString:@"time"]){CABasicAnimation*animation=[CABasicAnimationanimationWithKeyPath:key];animation.timingFunction=[CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionLinear];animation.fromValue=@(self.time);returnanimation;}return[superactionForKey:key];}
复制代码 如今,假如我们再次设置time属性,我们就会看到-display被屡次挪用。挪用的次数约莫为每秒60次,至于动画的长度,默许为0.25秒,约莫是15帧:- 2014-04-2822:37:04.253ClockFace[49145:60b]time:1.5000002014-04-2822:37:04.255ClockFace[49145:60b]time:1.5000002014-04-2822:37:04.351ClockFace[49145:60b]time:1.5000002014-04-2822:37:04.370ClockFace[49145:60b]time:1.5000002014-04-2822:37:04.388ClockFace[49145:60b]time:1.5000002014-04-2822:37:04.407ClockFace[49145:60b]time:1.5000002014-04-2822:37:04.425ClockFace[49145:60b]time:1.5000002014-04-2822:37:04.443ClockFace[49145:60b]time:1.5000002014-04-2822:37:04.461ClockFace[49145:60b]time:1.5000002014-04-2822:37:04.479ClockFace[49145:60b]time:1.5000002014-04-2822:37:04.497ClockFace[49145:60b]time:1.5000002014-04-2822:37:04.515ClockFace[49145:60b]time:1.5000002014-04-2822:37:04.755ClockFace[49145:60b]time:1.500000
复制代码 因为某些缘故原由,当我们在每一个两头点打印time值时,我们一向看到的是终极值。为什么不克不及失掉插值呢?由于我们检察的是毛病的time属性。
当你设置某个CALayer的某个属性,你实践设置的是modelLayer的值——这里的modelLayer暗示正在举行的动画停止时,Layer所到达的终极形态。假如你取modelLayer的值,它就老是给你它被设置到的终极值。
但毗连到modelLayer的是所谓的presentationLayer——它是modelLayer的一个拷贝,但它的值所暗示的是以后的,两头动画形态。假如我们修正-display办法往打印Layer的presentationLayer的time属性,那我们就会看到我们所希冀的插值。(同时我们也利用presentationLayer的time属性来猎取动画的入手下手值,替换self.time):- @interfaceViewController()@property(nonatomic,strong)IBOutletUIDatePicker*datePicker;@property(nonatomic,strong)ClockFace*clockFace;@end@implementationViewController-(void)viewDidLoad{[superviewDidLoad];//增加时钟面板Layerself.clockFace=[[ClockFacealloc]init];self.clockFace.position=CGPointMake(self.view.bounds.size.width/2,150);[self.view.layeraddSublayer:self.clockFace];//设置默许工夫self.clockFace.time=[NSDatedate];}-(IBAction)setTime{self.clockFace.time=self.datePicker.date;}@end0
复制代码 上面是打印出的值:- @interfaceViewController()@property(nonatomic,strong)IBOutletUIDatePicker*datePicker;@property(nonatomic,strong)ClockFace*clockFace;@end@implementationViewController-(void)viewDidLoad{[superviewDidLoad];//增加时钟面板Layerself.clockFace=[[ClockFacealloc]init];self.clockFace.position=CGPointMake(self.view.bounds.size.width/2,150);[self.view.layeraddSublayer:self.clockFace];//设置默许工夫self.clockFace.time=[NSDatedate];}-(IBAction)setTime{self.clockFace.time=self.datePicker.date;}@end1
复制代码 以是如今我们所要做就是画出时钟。我们将利用一般的CoreGraphics函数以绘制到一个GraphicsContext下去做到这一点,然后将发生出图象设置为我们Layer的contents。上面是更新后的-display办法:- @interfaceViewController()@property(nonatomic,strong)IBOutletUIDatePicker*datePicker;@property(nonatomic,strong)ClockFace*clockFace;@end@implementationViewController-(void)viewDidLoad{[superviewDidLoad];//增加时钟面板Layerself.clockFace=[[ClockFacealloc]init];self.clockFace.position=CGPointMake(self.view.bounds.size.width/2,150);[self.view.layeraddSublayer:self.clockFace];//设置默许工夫self.clockFace.time=[NSDatedate];}-(IBAction)setTime{self.clockFace.time=self.datePicker.date;}@end2
复制代码 了局看起来以下:
<br>
如你所见,分歧于第一个时钟动画,跟着时针的变更,分针实践上对每个小时城市转上满满一圈(就像一个真实的时钟那样),而不单单只是经由过程最短的路径挪动到它的终极地位;由于我们正在动画的是time值自己而不单单是时针或分针的地位,以是高低文信息被保存了。
经由过程如许的体例绘制一个时钟并非很幻想,由于CoreGraphics函数没有硬件减速,大概会引发动画帧数的下落。另外一种能每秒重绘contents图象60次的体例是用一个数组存储一些事后绘制好的图象,然后基于符合的插值复杂的选择对应的图象便可。完成代码也许以下:- @interfaceViewController()@property(nonatomic,strong)IBOutletUIDatePicker*datePicker;@property(nonatomic,strong)ClockFace*clockFace;@end@implementationViewController-(void)viewDidLoad{[superviewDidLoad];//增加时钟面板Layerself.clockFace=[[ClockFacealloc]init];self.clockFace.position=CGPointMake(self.view.bounds.size.width/2,150);[self.view.layeraddSublayer:self.clockFace];//设置默许工夫self.clockFace.time=[NSDatedate];}-(IBAction)setTime{self.clockFace.time=self.datePicker.date;}@end3
复制代码 经由过程制止在每帧里都用高贵的软件绘制,我们能改良动画的功能,但价值是我们必要在内存里存储一切事后绘制的动画帧图象,关于一个庞大的动画来讲,这大概形成惊人的内存华侈。
但这提出了一个风趣的大概性。假如我们完整不在-display里更新contents图象会产生甚么?我们做一些别的的事变如何?
非可视属性的动画
在-display里更新别的Layer属性就是不用要的,由于我们能够很复杂地间接对任何如许的属性做动画,好像我们在第一个时钟面板例子里所做的那样。但假如我们设置一些别的的工具,好比某些完整和Layer不相干的工具,会如何呢?
上面的代码利用一个CALayer分离AVAudioPlayer来创立一个可动画的音量把持器。经由过程把音量绑定到dynamic的Layer属性上,我们可使用CoreAnimation的属性插值来光滑的在两个分歧的音量之间突变,以一样的体例我们能够动画Layer上的任何自界说属性:- @interfaceViewController()@property(nonatomic,strong)IBOutletUIDatePicker*datePicker;@property(nonatomic,strong)ClockFace*clockFace;@end@implementationViewController-(void)viewDidLoad{[superviewDidLoad];//增加时钟面板Layerself.clockFace=[[ClockFacealloc]init];self.clockFace.position=CGPointMake(self.view.bounds.size.width/2,150);[self.view.layeraddSublayer:self.clockFace];//设置默许工夫self.clockFace.time=[NSDatedate];}-(IBAction)setTime{self.clockFace.time=self.datePicker.date;}@end4
复制代码 我们能够经由过程利用一个复杂的有着播放、中断、音量增年夜和音量减小按钮的ViewController来做测试:
Model的改变最好通过Notification来传播之前吃过这样的亏最好不要用delegate模式)UIViewController |
|