The biggest difference between time and space is that you can't reuse time. 时间和空间最大的区别在于,时间不能被复用 -- 弗斯特梅里克

在上面两章中,我们探讨了可以用 CAAnimation 和它的子类实现的多种图层动 画。动画的发生是需要持续一段时间的,所以计时对整个概念来说至关重要。在这 一章中,我们来看看 CAMediaTiming ,看看Core Animation是如何跟踪时间的。 #CAMediaTiming 协议 CAMediaTiming协议定义了在一段动画内用来控制逝去时间的属性的集合,CALayerCAAnimation 都实现了这个协议,所以时间可以被任意基于一 个图层或者一段动画的类控制。 ##持续和重复 我们在第八章“显式动画”中简单提到过duration ( CAMediaTiming的属性之 一), duration 是一个CFTimeInterval的类型(类似于NSTimeInterval一种双精度浮点类型),对将要进行的动画的一次迭代指定了时间。

这里的一次迭代是什么意思呢? CAMediaTiming 另外还有一个属性叫做repeatCount,代表动画重复的迭代次数。如果 duration 是2,repeatCount设为3.5(三个半迭代),那么完整的动画时长将是7秒。 的

durationrepeatCount 默认都是0。但这不意味着动画时长为0秒,或者0 次,这里的0仅仅代表了“默认”,也就是0.25秒和1次,你可以用一个简单的测试来 尝试为这两个属性赋多个值,如清单9.1,图9.1展示了程序的结果。 清单9.1 测试 duration 和 repeatCount

@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, weak) IBOutlet UITextField *durationField;
@property (nonatomic, weak) IBOutlet UITextField *repeatField;
@property (nonatomic, weak) IBOutlet UIButton *startButton;
@property (nonatomic, strong) CALayer *shipLayer;
@end
@implementation ViewController
- (void)viewDidLoad {[super viewDidLoad];//add the shipself.shipLayer = [CALayer layer];self.shipLayer.frame = CGRectMake(0, 0, 128, 128); self.shipLayer.position = CGPointMake(150, 150); self.shipLayer.contents = (__bridge id)[UIImage imageNamed:@"Ship.png"].CGImage; [self.containerView.layer addSublayer:self.shipLayer];
}
- (void)setControlsEnabled:(BOOL)enabled {//UIControl是UIView的子类,当然也是UIResponder的子类。UIControl是诸如UIButton、UISwitch、UITextField等控件的父类,它本身也包含了一些属性和方法,但是不能直接使用UIControl类,它只是定义了子类都需要使用的方法。dfor (UIControl *control in @[self.durationField, self.repeatField, self.startButton]){control.enabled = enabled; control.alpha = enabled? 1.0f: 0.25f;}
}
- (IBAction)hideKeyboard {[self.durationField resignFirstResponder];[self.repeatField resignFirstResponder];
}
- (IBAction)start {CFTimeInterval duration = [self.durationField.text doubleValue]; float repeatCount = [self.repeatField.text floatValue];//animate the ship rotationCABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"transform.rotation"; animation.duration = duration;animation.repeatCount = repeatCount;animation.byValue = @(M_PI * 2);animation.delegate = self;[self.shipLayer addAnimation:animation forKey:@"rotateAnimation"];//disable controls[self setControlsEnabled:NO];
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {//reenable controls[self setControlsEnabled:YES];
}
@end
复制代码

创建重复动画的另一种方式是使用repeatDuration属性,它让动画重复一个指定的时间,而不是指定次数。你甚至设置一个叫做autoreverses的属性(BOOL 类型)在每次间隔交替循环过程中自动回放。这对于播放一段连续非循环的动画很 有用,例如打开一扇门,然后关上它(图9.2)。对门进行摆动的代码见清单9.2。我们用了autoreverses来使门在打开后自动关闭,在这里我们把repeatDuration设置为INFINITY,于是动画无限循环播放,设置repeatCountINFINITY也有同样的效果。注意repeatDurationrepeatCount可能会相互冲突,所以你只要对其中一个 指定非零值。对两个属性都设置非0值的行为没有被定义。

清单9.2 使用 autoreverses 属性实现门的摇摆

@interface ViewController ()
@property (nonatomic, weak) UIView *containerView;
@end
@implementation ViewController
- (void)viewDidLoad {[super viewDidLoad];//add the doorCALayer *doorLayer = [CALayer layer];doorLayer.frame = CGRectMake(0, 0, 128, 256); doorLayer.position = CGPointMake(150 - 64, 150); doorLayer.anchorPoint = CGPointMake(0, 0.5); doorLayer.contents = (__bridge id)[UIImage imageNamed:@"Door.png"].CGImage;   [self.containerView.layer addSublayer:doorLayer];//apply perspective transformCATransform3D perspective = CATransform3DIdentity; perspective.m34 = -1.0 / 500.0; //sublayerTransform : 对其子layer进行3D变换 self.containerView.layer.sublayerTransform = perspective;//运用摆动动画CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"transform.rotation.y"; animation.toValue = @(-M_PI_2);animation.duration = 2.0;animation.repeatDuration = INFINITY; animation.autoreverses = YES;[doorLayer addAnimation:animation forKey:nil];
}
@end
复制代码

#相对时间 每次讨论到Core Animation,时间都是相对的,每个动画都有它自己描述的时间,可以独立地加速,延时或者偏移。

beginTime 指定了动画开始之前的的延迟时间。这里的延迟从动画添加到可见图 层的那一刻开始测量,默认是0(就是说动画会立刻执行)。

speed 是一个时间的倍数,默认1.0,减少它会减慢图层/动画的时间,增加它会加快速度。如果2.0的速度,那么对于一个duration 为1的动画,实际上在0.5秒 的时候就已经完成了。

timeOffsetbeginTime类似,但是和增加beginTime导致的延迟动画不同,增加timeOffset只是让动画快进到某一点,例如,对于一个持续1秒的动画来说,设置timeOffset为0.5意味着动画将从一半的地方开始。

beginTime不同的是,timeOffset不受speed影响。所以如果你把speed设为2.0,把timeOffset设置为0.5,那么你的动画将从动画最后结束 的地方开始,因为1秒的动画实际上被缩短到了0.5秒。然而即使使用了timeOffset让动画从结束的地方开始,它仍然播放了一个完整的时长,这个动画仅仅是循环了一圈,然后从头开始播放。

可以用清单9.3的测试程序验证一下,设置 speedtimeOffset 滑块到随意的值,然后点击播放来观察效果(见图9.3)

清单9.3 测试 timeOffsetspeed 属性

@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, weak) IBOutlet UILabel *speedLabel;
@property (nonatomic, weak) IBOutlet UILabel *timeOffsetLabel;
@property (nonatomic, weak) IBOutlet UISlider *speedSlider;
@property (nonatomic, weak) IBOutlet UISlider *timeOffsetSlider;
@property (nonatomic, strong) UIBezierPath *bezierPath;
@property (nonatomic, strong) CALayer *shipLayer;
@end
@implementation ViewController
- (void)viewDidLoad {[super viewDidLoad];//create a pathself.bezierPath = [[UIBezierPath alloc] init]; [self.bezierPath moveToPoint:CGPointMake(0, 150)]; [self.bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];//draw the path using a CAShapeLayerCAShapeLayer *pathLayer = [CAShapeLayer layer]; pathLayer.path = self.bezierPath.CGPath; pathLayer.fillColor = [UIColor clearColor].CGColor; pathLayer.strokeColor = [UIColor redColor].CGColor; pathLayer.lineWidth = 3.0f; [self.containerView.layer addSublayer:pathLayer];//add the shipself.shipLayer = [CALayer layer];self.shipLayer.frame = CGRectMake(0, 0, 64, 64); self.shipLayer.position = CGPointMake(0, 150); self.shipLayer.contents = (__bridge id)[UIImage imageNamed:@"Ship.png"].CGImage; [self.containerView.layer addSublayer:self.shipLayer];//set initial values[self updateSliders];
}
- (IBAction)updateSliders {CFTimeInterval timeOffset = self.timeOffsetSlider.value; self.timeOffsetLabel.text = [NSString stringWithFormat:@"%0.2f", timeOffset];float speed = self.speedSlider.value;self.speedLabel.text = [NSString stringWithFormat:@"%0.2f", speed];
}
- (IBAction)play
{//create the keyframe animationCAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"position";animation.timeOffset = self.timeOffsetSlider.value; animation.speed = self.speedSlider.value;animation.duration = 1.0;animation.path = self.bezierPath.CGPath; animation.rotationMode = kCAAnimationRotateAuto; animation.removedOnCompletion = NO;[self.shipLayer addAnimation:animation forKey:@"slide"];
}
@end
复制代码

#fillMode 对于 beginTime 非0的一段动画来说,会出现一个当动画添加到图层上但什么也没发生的状态。类似的, removeOnCompletion 被设置为 NO 的动画将会在动画结束的时候仍然保持之前的状态。这就产生了一个问题,当动画开始之前和动画结 束之后,被设置动画的属性将会是什么值呢?

一种可能是属性和动画没被添加之前保持一致,也就是在模型图层定义的值(见第 七章“隐式动画”,模型图层和呈现图层的解释)。

另一种可能是保持动画开始之前那一帧,或者动画结束之后的那一帧。这就是所谓的填充,因为动画开始和结束的值用来填充开始之前和结束之后的时间。

这种行为就交给开发者了,它可以被 CAMediaTimingfillMode 来控 制。 fillMode 是一个 NSString 类型,可以接受如下四种常量:

kCAFillModeForwards
kCAFillModeBackwards
kCAFillModeBoth
kCAFillModeRemoved
复制代码

默认是 kCAFillModeRemoved ,当动画不再播放的时候就显示图层模型指定的值剩下的三种类型向前,向后或者即向前又向后去填充动画状态,使得动画在开始前或者结束后仍然保持开始和结束那一刻的值。

这就对避免在动画结束的时候急速返回提供另一种方案(见第八章)。但是记住了,当用它来解决这个问题的时候,需要把 removeOnCompletion 设置为 NO , 另外需要给动画添加一个非空的键,于是可以在不需要动画的时候把它从图层上移 除。 #层级关系时间 在第三章“图层几何学”中,你已经了解到每个图层是如何相对在图层树中的父图层定义它的坐标系的。动画时间和它类似,每个动画和图层在时间上都有它自己的层级概念,相对于它的父亲来测量。对图层调整时间将会影响到它本身和子图层的动 画,但不会影响到父图层。另一个相似点是所有的动画都被按照层级组合(使 用 CAAnimationGroup 实例)。

CALayer或者CAGroupAnimation调整durationrepeatCount/repeatDuration属性并不会影响到子动画。然而在层级关系中,beginTime 指定了父图层开始动画(或者组合关系中的父动画)和对象将要开始自己动画之间的偏移。类似的,调整 CALayerCAGroupAnimationspeed 属性将会对动画以及子动画速度应 用一个缩放的因子。

#全局时间和本地时间 CoreAnimation有一个全局时间的概念,也就是所谓的马赫时间(“马赫”实际上是 iOS和Mac OS系统内核的命名)。马赫时间在设备上所有进程都是全局的--但是在 不同设备上并不是全局的--不过这已经足够对动画的参考点提供便利了,你可以使 用 CACurrentMediaTime 函数来访问马赫时间:

CFTimeInterval time = CACurrentMediaTime();

这个函数返回的值其实无关紧要(它返回了设备自从上次启动后的秒数,并不是你所关心的),它真实的作用在于对动画的时间测量提供了一个相对值。注意当设备休眠的时候马赫时间会暂停,也就是所有的 CAAnimations (基于马赫时间)同样也会暂停。

因此马赫时间对长时间测量并不有用。比如用CACurrentMediaTime去更新一个实时闹钟并不明智。(可以用[NSDate date]代替,就像第三章例子所示)。

每个 CALayerCAAnimation 实例都有自己本地时间的概念,是根据父图层/动画层级关系中的beginTime, , timeOffsetspeed属性计算。就和转换不同图层之间坐标关系一样, CALayer 同样也提供了方法来转换不同图层之间的本地 时间。如下:

- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(CALayer *)l;
- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(CALayer *)l;
复制代码

当用来同步不同图层之间有不同的 speedtimeOffsetbeginTime 的动 画,这些方法会很有用。
#暂停,倒回和快进 设置动画的 speed 属性为0可以暂停动画,但在动画被添加到图层之后不太可能再修改它了,所以不能对正在进行的动画使用这个属性。给图层添加一个 CAAnimation 实际上是给动画对象做了一个不可改变的拷贝,所以对原始动画 对象属性的改变对真实的动画并没有作用。相反,直接用 -animationForKey:来检索图层正在进行的动画可以返回正确的动画对象,但是修改它的属性将会抛出异常。

如果移除图层正在进行的动画,图层将会急速返回动画之前的状态。但如果在动画移除之前拷贝呈现图层到模型图层,动画将会看起来暂停在那里。但是不好的地方在于之后就不能再恢复动画了。

一个简单的方法是可以利用 CAMediaTiming 来暂停图层本身。如果把图层的 speed 设置成0,它会暂停任何添加到图层上的动画。类似的,设置 speed 大 于1.0将会快进,设置成一个负值将会倒回动画。

通过增加主窗口图层的 speed ,可以暂停整个应用程序的动画。这对UI自动化提 供了好处,我们可以加速所有的视图动画来进行自动化测试(注意对于在主窗口之 外的视图并不会被影响,比如 UIAlertview )。可以在app delegate设置如下进 行验证: self.window.layer.speed = 100; 你也可以通过这种方式来减速,但其实也可以在模拟器通过切换慢速动画来实现。 #手动动画 timeOffset一个很有用的功能在于你可以它可以让你手动控制动画进程,通过 设置speed 为0,可以禁用动画的自动播放,然后来使用timeOffset来来回显示动画序列。这可以使得运用手势来手动控制动画变得很简单。

举个简单的例子:还是之前关门的动画,修改代码来用手势控制动画。我们给视图 添加一个UIPanGestureRecognizer ,然后用timeOffset左右摇晃。

因为在动画添加到图层之后不能再做修改了,我们来通过调 整 layertimeOffset 达到同样的效果(清单9.4)。 清单9.4 通过触摸手势手动控制动画

@interface ViewController ()
@property (nonatomic, weak) UIView *containerView;
@property (nonatomic, strong) CALayer *doorLayer;
@end
@implementation ViewController
- (void)viewDidLoad
{[super viewDidLoad];//add the doorself.doorLayer = [CALayer layer]; self.doorLayer.frame = CGRectMake(0, 0, 128, 256); self.doorLayer.position = CGPointMake(150 - 64, 150); //锚点self.doorLayer.anchorPoint = CGPointMake(0, 0.5); self.doorLayer.contents = (__bridge id)[UIImage imageNamed:@"Door.png"].CGImage; [self.containerView.layer addSublayer:self.doorLayer];//apply perspective transformCATransform3D perspective = CATransform3DIdentity; perspective.m34 = -1.0 / 500.0; self.containerView.layer.sublayerTransform = perspective;//add pan gesture recognizer to handle swipesUIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] init]; [pan addTarget:self action:@selector(pan:)];[self.view addGestureRecognizer:pan];//pause all layer animationsself.doorLayer.speed = 0.0;//apply swinging animation (which won't play because layer is paused)CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"transform.rotation.y"; animation.toValue = @(-M_PI_2);animation.duration = 1.0;[self.doorLayer addAnimation:animation forKey:nil];}
- (void)pan:(UIPanGestureRecognizer *)pan {//获取平移手势的水平分量CGFloat x = [pan translationInView:self.view].x;//convert from points to animation duration //using a reasonable scale factorx /= 200.0f;//update timeOffset and clamp resultCFTimeInterval timeOffset = self.doorLayer.timeOffset;timeOffset = MIN(0.999, MAX(0.0, timeOffset - x)); self.doorLayer.timeOffset = timeOffset;//reset pan gesture[pan setTranslation:CGPointZero inView:self.view];
}
@end
复制代码

这其实是个小诡计,也许相对于设置个动画然后每次显示一帧而言,用移动手势来直接设置门的 transform会更简单。

在这个例子中的确是这样,但是对于比如说关键这这样更加复杂的情况,或者有多个图层的动画组,相对于实时计算每个图层的属性而言,这就显得方便的多了。

#总结 在这一章,我们了解了 CAMediaTiming 协议,以及Core Animation用来操作时间 控制动画的机制。在下一章,我们将要接触缓冲 ,另一个用来使动画更加真实的操作时间的技术。

iOS核心动画高级技术(九) 图层时间相关推荐

  1. iOS核心动画高级技术(十二) 性能调优

    Code should run as fast as necessary, but no faster. 代码应该运行的尽量快,而不是更快 - 理查德 在第一和第二部分,我们了解了Core Anima ...

  2. iOS核心动画高级技术(十四) 图像IO

    The idea of latency is worth thinking about. 潜伏期值得思考 - 凯文 帕萨特 在第13章"高效绘图"中,我们研究了和Core Grap ...

  3. iOS核心动画高级技术(十三) 高效绘图

    More computing sins are committed in the name of efficiency (without necessarily achieving it) than ...

  4. IOS核心动画高级五:变换

    在第四章"视觉效果"中,我们研究了一些增强图层和它的内容显示效果的一些技术,在这一章中,我们将要研究可以用来对图层旋转.摆放或者扭曲的CGAffineTransform.以及可以将 ...

  5. iOS核心动画详解swift版----基础动画

    2019独角兽企业重金招聘Python工程师标准>>> iOS核心动画详解swift版---基础动画 创建工程,添加2个ViewController,通过rootViewContro ...

  6. iOS核心动画学习整理

    最近利用业余时间终于把iOS核心动画高级技巧(https://zsisme.gitbooks.io/ios-/content/chapter1/the-layer-tree.html)看完,对应其中一 ...

  7. iOS 核心动画 Core Animation浅谈

    代码地址如下: http://www.demodashi.com/demo/11603.html 前记 关于实现一个iOS动画,如果简单的,我们可以直接调用UIView的代码块来实现,虽然使用UIVi ...

  8. iOS核心动画以及UIView动画的介绍

    我们看到很多App带有绚丽狂拽的特效,别出心裁的控件设计,很大程度上提高了用户体验,在增加了实用性的同时,也赋予了app无限的生命力.这些华丽的效果很多都是基于iOS的核心动画原理实现的,本文介绍一些 ...

  9. iOS核心动画Core Animation(一)

    核心动画Core Animation(一) 一.简述 Core Animation是直接作用在CALayer上的(并非UIView上)非常强大的跨Mac OS X和iOS平台的动画处理API,Core ...

最新文章

  1. mysql sysdate() 慢_mysql笔记
  2. NYOJ 289 苹果
  3. linux局部变量特殊字符替换,变量,全局变量,环境变量,特殊符号、管道符命令:cut、sort、uniq、wc、tee、tr、sp...
  4. EntLib.com Forum / YAF 开源论坛--源码的目录结构(对分析代码很有帮助)
  5. JAVA格式化同一天时间间隔_java中时间格式化怎么去时间间隔值
  6. 如何理解SVM | 支持向量机之我见
  7. 大数据学习笔记51:Flume Channel Selectors(Flume通道选择器)
  8. WIN10+Ubuntu16.4 双系统,遇到的坑
  9. android 下拉刷新listview,实现Android下拉刷新的ListView
  10. Mysql 常用函数集
  11. 蒋勇 | 白话区块链技术栈与应用
  12. 使用虚拟机VMware 15 pro安装Ubuntu 16.04 LTS
  13. vue实现视频播放器功能,你学会了吗
  14. dblp搜文献时各颜色含义
  15. CIS crosstalk简介
  16. MIoU(均交并比)的计算
  17. placement new理解
  18. 计算机汉字字模信息怎么算,汉字字模库字模.PPT
  19. 【计算理论】图灵机 ( 图灵机示例 )
  20. 【基于AnyLogic的管理仿真系统】

热门文章

  1. 成都Uber优步司机奖励政策(3月23日)
  2. linux ps 命令的结果中VSZ,RSS,STAT的含义和大小
  3. ASP.NET中自动生成XML文件并通过XSLT显示在网页中的方法
  4. 面向对象的JavaScript基本知识指南大全
  5. SSIS工具的ETL过程,全量ETL和增量ETL过程教程
  6. Linux下导入SQL文件
  7. 软件测试流程和项目管理流程
  8. jmeter脚本写个小demo(html论坛自动发帖、json龙果学院-前后端分离)
  9. Spring MVC 自定义验证器示例
  10. JavaScript从入门到放弃-JavaScript历史介绍