图层时间
1. CAMediaTiming
协议
动画的发生需要持续一段时间, 所以计时对整个动画概念来说都非常重要. CAMediaTiming协议定义了在一段时间内用来控制逝去时间的属性的集合, CALayer
和CAAnimation
都实现了这个协议, 所以时间可以被任意基于一个图层或者一段动画的类控制.
CAMediaTiming协议
/* The CAMediaTiming protocol is implemented by layers and animations, it
* models a hierarchical timing system, with each object describing the
* mapping from time values in the object's parent to local time.
*
* Absolute time is defined as mach time converted to seconds. The
* CACurrentMediaTime function is provided as a convenience for querying the
* current absolute time.
*
* The conversion from parent time to local time has two stages:
*
* 1. conversion to "active local time". This includes the point at
* which the object appears in the parent's timeline, and how fast it
* plays relative to the parent.
*
* 2. conversion from active to "basic local time". The timing model
* allows for objects to repeat their basic duration multiple times,
* and optionally to play backwards before repeating. */
@class NSString;
NS_ASSUME_NONNULL_BEGIN
@protocol CAMediaTiming
/* The begin time of the object, in relation to its parent object, if
* applicable. Defaults to 0. */
@property CFTimeInterval beginTime;
/* The basic duration of the object. Defaults to 0. */
@property CFTimeInterval duration;
/* The rate of the layer. Used to scale parent time to local time, e.g.
* if rate is 2, local time progresses twice as fast as parent time.
* Defaults to 1. */
@property float speed;
/* Additional offset in active local time. i.e. to convert from parent
* time tp to active local time t: t = (tp - begin) * speed + offset.
* One use of this is to "pause" a layer by setting `speed' to zero and
* `offset' to a suitable value. Defaults to 0. */
@property CFTimeInterval timeOffset;
/* The repeat count of the object. May be fractional. Defaults to 0. */
@property float repeatCount;
/* The repeat duration of the object. Defaults to 0. */
@property CFTimeInterval repeatDuration;
/* When true, the object plays backwards after playing forwards. Defaults
* to NO. */
@property BOOL autoreverses;
/* Defines how the timed object behaves outside its active duration.
* Local time may be clamped to either end of the active duration, or
* the element may be removed from the presentation. The legal values
* are `backwards', `forwards', `both' and `removed'. Defaults to
* `removed'. */
@property(copy) NSString *fillMode;
@end
/* `fillMode' options. */
CA_EXTERN NSString * const kCAFillModeForwards
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);
CA_EXTERN NSString * const kCAFillModeBackwards
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);
CA_EXTERN NSString * const kCAFillModeBoth
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);
CA_EXTERN NSString * const kCAFillModeRemoved
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);
NS_ASSUME_NONNULL_END
2. 持续和重复
**duration:**它是一种双精度浮点类型, 对将要进行的动画的一次迭代指定了时间. 简单的说, 就是指定一个动画全过程持续的时间.
**repeatCount:**顾名思义, 代表动画重复次数, 如果duration=2, repeatCount=3.5, 那么完整的动画时长将是7s.
duration和repeatCount默认值都是0, 但是并不代表动画时长和重复次数都是0, "0"紧紧代表是默认情况. 默认情况下动画时长0.25s, 重复1次.
**repeatDuration:**创建重复动画的另一种方式是repeatDuration, 顾名思义, 重复持续时长, 代表动画重复执行的完整时长. 它让动画重复一个指定的时长而不是指定的次数.
**autoreverses:**BOOL值, 表示是否开启动画自动回放. 设置为YES, 在连续非循环动画中, 当动画结束之后, 动画将从后往前回放.
当把repeatDuration设置为INFINITY
, 于是动画将无限循环播放, 设置repeatCount为INFINIty
也有同样的效果. 同事设置repeatDuration和repeatCount可能会导致相互冲突, 只需指定其中一个非零即可, 两个值均设置为非零的状态没有被定义.
3. 相对时间
每次讨论到CoreAnimation, 时间都是相对的, 每个动画都有它自己的描述时间, 可以独立的进行加速,延时或者偏移.
**beginTime: **它指定了动画开始之前的延迟时间, 这里的延迟是从动画被添加到图层的那一刻开始测量的, 默认是0.
**speed: **它是一个时间的倍数, 默认是1.0, 减少相当于图层上动画减速执行, 增加相当于是她图层上面的动画加速执行, 比如, 一个2s的动画, 如果speed=2, 那么动画实际执行时间是1s.
**timeOffset: **顾名思义, 时间偏移量. 当增加它时候, 会使动画快进到某一点, 例如, 对于一个持续1s的动画, timeOffset=0.5, 意味着动画将从一半的时候开始.
beginTime会受speed的影响, 但是timeOffSet并不受speed的影响. 所以如果1s的动画, speed=2, timeOffSet=0.5, 那么动画将从最后结束的位置开始执行, 因为1s动画实际上被缩短到0.5s. 经过测试, timeOffSet只是改变了动画开始的位置, 实际上动画还是会执行完整个过程的, 比如1s的动画, timeOffSet=0.5, 那么动画将从一半的位置开始执行, 按照动画时间执行完后半部分, 然后马上返回执行动画的前半部分, 当动画执行到动画开始位置时候, 动画结束.
4. fillMode
对于beginTime非0的动画来说, 会出现一个当动画被添加到图层上但什么也没发生的状态. 类似的, removeOnCompletion=NO, 动画将在动画结束之后仍然保持之前的状态. 这就产生一个问题, 当动画开始之前, 和动画结束之后, 被设置动画的属性将会是什么值呢? CAMediaTiming的 fillMode
就是用来解决这个问题的.
CA_EXTERN NSString * const kCAFillModeForwards; //保持之前的状态
CA_EXTERN NSString * const kCAFillModeBackwards; //保持之后的状态
CA_EXTERN NSString * const kCAFillModeBoth; //动画开始前保持开始前装填, 结束后保持结束后状态
CA_EXTERN NSString * const kCAFillModeRemoved; //默认状态, 移除
默认值是kCAFillModeRemoved
, 当动画不再播放时候显示图层模型. 指定值剩余三种类型, 向前, 向后, 既向前,又向后去填充动画状态, 使得动画在开始前和结束后仍然保持开始和结束那一刻的状态.
这就对避免动画结束后急速返回提供另一种解决方案. 但是要记住, 当用它来解决问题时候, 需要把removeOnCompletion设置为NO, 另外需要添加一个非空的键, 于是可以在不需要的时候将动画从图层上移除.
5. 层级关系时间
在第三章中, 了解到了每个图层是如何相对于父图层定义它的坐标的. 动画时间和这个类似, 每个动画和图层在时间上都有它自己的层级关系, 相对于它的父亲来测量. 对图层调整时间, 将会影响到它本身和子图层的动画, 但不会影响到父图层. 另外, 所有的动画都是按照层级关系组合的(使用CAAnimationGroup实例).
对CALayer或者CAAnimationGroup调整duration
/repeatCount
/repeatDuration
属性并不会影响到子动画, 但是调整beginTime
/timeOffset
/speed
属性将会影响到子动画. 然而在层级关系中, ceginTIme指定了父图层开始动画(或者组合关系中的父动画)和对象将要开始自己动画之间的偏移. 类似的, CALayer和CAAnimationGroup的speed属性将会对动画以及子动画速度应用一个缩放因子.
5. 全局时间和本地时间
CoreAnimation有一个全局时间的概念, 也就是所谓的马赫时间(马赫实际上是iOS和Mac OS系统内核的明明). 马赫时间在设备上所有进程都是全局的. --但是在不同设备上并不是全局的--这是为了对动画提供便利. 我们可以通过函数来访问设备的马赫时间:
CFTimeInterval time = CACurrentMediaTime();
NSLog(@"马赫时间: %lf", time);
马赫时间的作用是对动画时间测量提供一个相对值, 当设备休眠时候, 马赫时间也会暂停, 也就是说, 所有CAAnimations(基于马赫时间)都会暂停.
每个CALayer和CAAnimation都有自己本地时间的概念, 这是根据父视图/动画层级关系中的beginTime,timeOffset和speed属性计算来, CALayer提供了方法来转换不同图层之间的本地时间.
- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(nullable CALayer *)l;
- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(nullable CALayer *)l;
6. 暂停,倒回, 快进
设置动画的speed属性为0可以暂停动画, 但是在动画被添加到图层之后就不太可能再去修改它了, 所以不能对正在进行的动画使用这个属性. 给图层添加一个CAAnimation实际上是给动画做了一个不可改变的拷贝, 所以对原始对象属性的改变对真是的动画并没有什么卵用. 直接用-animationForKey:
方法检索正在进行的动画并修改它的属性值, 会导致抛出异常.
一个简单的方法就是利用CAMediaTiming来暂停图层本身. 如果把图层的speed设置为0, 它会暂停任何添加到图层上的动画. speed大于1, 快进, 设置为负值, 则会倒退.
通过更改主窗口图层的speed, 可以暂停整个应用程序的动画. 如:
//可以通过这里对整个窗口的动画进行减速或者加速.
self.window.layer.speed = 0.1;
以下代码, 第一个函数是暂停动画, 暂停动画还是比较简单的, 也比较容易理解, 就是将马赫时间转换成layer上面的时间, 然后设置layer速度为0, 设置时间偏移量. 继续执行动画, 代码稍复杂点, 先获取被暂停的时间, 然后设置动画的开始时间是当前马赫时间和暂停时候马赫时间之间的间隔. 自己也写了些继续动画代码, 但是不起作用, 如下代码是网上找的, 但是也存在问题,* 当speed=1时候, 暂停和继续都可以, 但是speed非1, 继续动画时候, 就不行了. 暂时不知道为什么, 如果谁知道还请不吝赐教. *
//暂停动画
- (void)pauseLayer:(CALayer*)layer
{
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;
}
//继续layer上面的动画
- (void)resumeLayer:(CALayer*)layer
{
CFTimeInterval pausedTime = [layer timeOffset];
layer.speed = 0.5;
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
}
6. 手动动画
timeOffset
一个很有用的功能是可以自己手动控制动画的进程, 通过设置图层的speed=0, 禁用动画的自动播放, 然后通过timeOffset来来回回显示动画的序列. 这样可以方便的使用手势控制动画.
/**
* 手动动画
*/
- (void)controlAnimation {
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureChanged:)];
[self.view addGestureRecognizer:panGesture];
self.plane.layer.speed = 0.0;
}
// 动画时长是3s
- (void)panGestureChanged:(UIPanGestureRecognizer *)pan {
//获取拖拽在x轴上的变化
float x = [pan translationInView:self.view].x;
// 换算成时间偏移的单位
x = x / 60;
CFTimeInterval timeOffset = self.plane.layer.timeOffset;
//限制timeOffset, 最小是0, 最大是2.999
timeOffset = MIN(2.999, MAX(0.0, timeOffset + x));
//给layer设置时间偏移量
self.plane.layer.timeOffset = timeOffset;
//初始化偏移量为0,
[pan setTranslation:CGPointZero inView:self.view];
}