隐式动画
所谓的隐式动画,之所以叫隐式是因为我们并没有指定任何动画的类型。我们仅仅改变了一个属性,然后CoreAnimation
来决定如何并且何时去做动画。
// 比如设置颜色,没有指定动画,就是一个隐式动画
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
动画事务
但当你改变一个属性,CoreAnimation
是如何判断动画类型和持续时间的呢?实际上动画执行的时间取决于当前事务的设置,动画类型取决于图层行为。
事务实际上是CoreAnimation
用来包含一系列属性动画集合的机制,任何用指定事务去改变可以做动画的图层属性都不会立刻发生变化,而是当事务一旦提交的时候开始用一个动画过渡到新值。
CATransaction
事务是通过CATransaction
类来做管理,这个类的设计有些奇怪,不像你从它的命名预期的那样去管理一个简单的事务,而是管理了一叠你不能访问的事务。
-
+ (void)begin;
显示开启事务。 -
+ (void)commit;
提交当前事务期间进行的所有更改。 -
+ (void)flush;
提交任何现存的隐式事务。 -
+(void)lock;
、+(void)unlock;
锁定和解锁全局锁的方法。 -
+(void)setAnimationDuration:(CFTimeInterval)dur;
定义添加到图层的动画的默认持续时间。 -
+(void)setAnimationTimingFunction:(nullable CAMediaTimingFunction *)function;
添加到图层的任何动画都将此值设置为其timingFunction
属性。在Mac OS X 10.6
中添加。 -
+ (void)setDisableActions:(BOOL)flag;
定义图层的-actionForKey:
方法是否用于为每个图层属性更改找到一个操作(也称为implicitanimation
)。默认为NO,即启用了隐式动画。 -
+ (void)setCompletionBlock:(nullable void (^)(void))block;
设置为非nil值后,一旦此事务组随后添加的所有动画都已完成(或已删除),块就被保证被调用(在主线程上) )。 如果在提交当前事务组之前没有添加动画(或者完成块被设置为不同的值),则将立即调用该块。 在Mac OS X 10.6
中添加。 -
+ (void)setValue:(nullable id)anObject forKey:(NSString *)key;
将任意键控数据与当前事务(即与当前线程)关联。
嵌套事务具有嵌套数据作用域,即读取一个键,搜索已设置它的最内层作用域,设置键总是将其设置在最内层作用域。NSString * const kCATransactionAnimationDuration NSString * const kCATransactionDisableActions NSString * const kCATransactionAnimationTimingFunction NSString * const kCATransactionCompletionBlock
- 实例
运行结果:- (void)initColorLayer { self.colorLayer = [CALayer layer]; self.colorLayer.bounds = CGRectMake(0, 0, 100, 100); self.colorLayer.position = self.view.center; self.colorLayer.backgroundColor = [UIColor redColor].CGColor; [self.view.layer addSublayer:self.colorLayer]; } - (void)testCATransaction { [CATransaction begin]; // 开启动画 [CATransaction setAnimationDuration:3.0]; // 动画时间 // 动画完成旋转 [CATransaction setCompletionBlock:^{ CGAffineTransform transform = self.colorLayer.affineTransform; transform = CGAffineTransformRotate(transform, M_PI_2); self.colorLayer.affineTransform = transform; }]; // 改变颜色动画 CGFloat red = arc4random() / (CGFloat)INT_MAX; CGFloat green = arc4random() / (CGFloat)INT_MAX; CGFloat blue = arc4random() / (CGFloat)INT_MAX; self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor; // 提交动画 [CATransaction commit]; }
CoreAnimation
CAMediaTiming
协议中定义了时间,速度,重复次数等。
-
beginTime
用来设置动画延时,若想延迟1秒,就设置为CACurrentMediaTime()+1
,其中CACurrentMediaTime()
为图层当前时间。 -
duration
动画的持续时间。 -
speed
动画速率,决定动画时间的倍率。当speed
为2时,动画时间为设置的duration
的1/2。 -
timeOffset
动画时间偏移量。比如设置动画时长为3秒,当设置timeOffset
为1.5时,当前动画会从中间位置开始,并在到达指定位置时,走完之前跳过的前半段动画。 -
repeatCount
动画的重复次数。 -
repeatDuration
动画的重复时间。 -
autoreverses
动画由初始值到最终值后,是否反过来回到初始值的动画。如果设置为YES,就意味着动画完成后会以动画的形式回到初始值。 -
fillMode
决定当前对象在非动画时间段的行为。比如动画开始之前,动画结束之后。
CAAnimation
核心动画基础类,不能直接使用。
-
timingFunction
控制动画的节奏。
系统提供的包括:CAMediaTimingFunction + (instancetype)functionWithName:(NSString *)name;
kCAMediaTimingFunctionLinear (匀速) kCAMediaTimingFunctionEaseIn (慢进快出) kCAMediaTimingFunctionEaseOut (快进慢出) kCAMediaTimingFunctionEaseInEaseOut (慢进慢出,中间加速) kCAMediaTimingFunctionDefault (默认) 当然也可通过自定义创建CAMediaTimingFunction。
-
delegate
CAAnimationDelegate
的代理属性。 -
removedOnCompletion
是否让图层保持显示动画执行后的状态,默认为YES,也就是动画执行完毕后从涂层上移除,恢复到执行前的状态,如果设置为NO,并且设置fillMode
为kCAFillModeForwards
,则保持动画执行后的状态。
CAPropertyAnimation
属性动画,针对对象的可动画属性进行效果的设置,不可直接使用。添加属性具体如下:
-
keyPath
CALayer
的某个属性名,并通过这个属性的值进行修改,达到相应的动画效果。 -
additive
属性动画是否以当前动画效果为基础,默认为NO。 -
cumulative
指定动画是否为累加效果,默认为NO。 -
valueFunction
此属性配合CALayer
的transform
属性使用。
CABasicAnimation
基础动画,通过keyPath
对应属性进行控制,添加属性如下:
-
fromValue
keyPath
相应属性的初始值。 -
toValue
keyPath
相应属性的结束值。 -
byValue
在不设置toValue
时,toValue
=fromValue
+byValue
,也就是在当前的位置上增加多少。
使用步骤
- 初始化动画并设置动画
keyPath
(keyPath
为指定动画效果的CALayer
的某个属性名,比如position
属性)。 - 设置动画其他属性,比如
delegate
,fromValue
,toValue
,duration
等。 - 利用
- (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key;
添加给指定layer添加动画。 - 利用
- (void)removeAllAnimations;
或者- (void)removeAnimationForKey:(NSString *)key;
方法停止所有或者指定动画。
实例
- (void)testBasicAnim {
// 生成一个CADisplayLink,我们来看一下我们上面说过的layer的层级结构:
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
displayLink.preferredFramesPerSecond = 5;
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
//初始化动画并设置keyPath
CABasicAnimation *basicAnim = [CABasicAnimation animationWithKeyPath:@"position"];
//到达位置
basicAnim.toValue = [NSValue valueWithCGPoint:CGPointMake(300, 600)];
//动画时间
basicAnim.duration = 3;
//动画节奏
basicAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
//添加动画
[self.colorLayer addAnimation:basicAnim forKey:nil];
}
- (void)handleDisplayLink:(CADisplayLink *)displayLink{
NSLog(@"modelLayer: %@, presentLayer: %@", [NSValue valueWithCGPoint:self.colorLayer.position], [NSValue valueWithCGPoint:self.colorLayer.presentationLayer.position]);
}
运行结果:

打印结果:
2018-02-09 20:13:02.816055+0800 Demo[4618:543044] modelLayer: NSPoint: {187.5, 333.5}, presentLayer: NSPoint: {187.50000084648718, 333.50000200523408}
2018-02-09 20:13:03.148215+0800 Demo[4618:543044] modelLayer: NSPoint: {187.5, 333.5}, presentLayer: NSPoint: {190.23548460099846, 339.98005907703191}
2018-02-09 20:13:03.481441+0800 Demo[4618:543044] modelLayer: NSPoint: {187.5, 333.5}, presentLayer: NSPoint: {198.87441610917449, 360.44472793862224}
2018-02-09 20:13:03.815781+0800 Demo[4618:543044] modelLayer: NSPoint: {187.5, 333.5}, presentLayer: NSPoint: {213.57247307896614, 395.26279178261757}
2018-02-09 20:13:04.148192+0800 Demo[4618:543044] modelLayer: NSPoint: {187.5, 333.5}, presentLayer: NSPoint: {232.99431838095188, 441.27098532021046}
2018-02-09 20:13:04.481537+0800 Demo[4618:543044] modelLayer: NSPoint: {187.5, 333.5}, presentLayer: NSPoint: {254.37940657138824, 491.92988312244415}
2018-02-09 20:13:04.815032+0800 Demo[4618:543044] modelLayer: NSPoint: {187.5, 333.5}, presentLayer: NSPoint: {273.88154715299606, 538.12828725576401}
2018-02-09 20:13:05.148542+0800 Demo[4618:543044] modelLayer: NSPoint: {187.5, 333.5}, presentLayer: NSPoint: {288.56812343001366, 572.91915461421013}
2018-02-09 20:13:05.481451+0800 Demo[4618:543044] modelLayer: NSPoint: {187.5, 333.5}, presentLayer: NSPoint: {297.23102152347565, 593.44059765338898}
2018-02-09 20:13:05.815403+0800 Demo[4618:543044] modelLayer: NSPoint: {187.5, 333.5}, presentLayer: NSPoint: {299.99999329447746, 599.99998411536217}
你会发现动画完成后跳回到开始的位置,我们看到的这种现象结合打印结果,我们可以确定:动画本身并没有改变model tree
的位置,我们看到的动画是presentation tree
运动的轨迹。
// 我们加上这两句
// 图层是否显示执行后的动画执行后的位置以及状态
basicAnim.removedOnCompletion = NO;
basicAnim.fillMode = kCAFillModeForwards;
运行结果: 动画完成后停在了结束位置。
打印结果: 还是一样。
当设置removedOnCompletion
属性为NO以及fillMode
属性为kCAFillModeForwards
后,也并未改变model tree
的位置,但是可以使动画结束后,防止presentation tree
被移除并回到动画开始的位置。
储存动画结束位置
所以并不建议使用这种方式来实现动画结束时,图层不跳转回原位的实现,我们应该在动画开始或者结束时重新设置它的位置:
// 设置代理
basicAnim.delegate = self;
[basicAnim setValue:[NSValue valueWithCGPoint:CGPointMake(300, 600)] forKey:@"positionToEnd"];
// 动画结束代理方法
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
// 设置layer的position
self.colorLayer.position = [[anim valueForKey:@"positionToEnd"] CGPointValue];
}
打印结果:
2018-02-09 20:50:34.809058+0800 Demo[5023:600166] modelLayer: NSPoint: {300, 600}, presentLayer: NSPoint: {300, 600}
我们发现了另一个问题,当动画完成后,它会重新从起点运动到终点。
关闭隐式动画
因为我们之前提到的,对于非根图层,设置它的可动画属性是有隐式动画的,那么我们需要关闭图层的隐式动画,我们就需要用到动画事务。
// 动画结束代理方法
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
// 开启事务
[CATransaction begin];
// 关闭隐式动画
[CATransaction setDisableActions:YES];
// 设置layer的position
self.colorLayer.position = [[anim valueForKey:@"positionToEnd"] CGPointValue];
// 提交事务
[CATransaction commit];
}
CASpringAnimation
带有初始速度以及阻尼指数等物理参数的属性动画。我们可以把它看成在不绝对光滑的地面上,一个弹簧拴着别小球。
-
mass
小球质量,影响惯性。 -
stiffness
弹簧的劲度系数。 -
damping
阻尼系数,地面的摩擦力。 -
initialVelocity
初始速度,相当于给小球一个初始速度(可正可负,方向不同)。 -
settlingDuration
结算时间,根据上述参数计算出的预计时间,相对于你设置的时间,这个时间比较准确。
实例
CASpringAnimation
是iOS9
才引入的动画类,效果类似于UIView
的spring
动画,不过比其增加了质量,劲度系数等属性的扩展,继承于CABaseAnimation
。
- (void)testSpringAnim {
CASpringAnimation *springAnim = [CASpringAnimation animationWithKeyPath:@"position"];
// 阻力系数
springAnim.damping = 2.0f;
// 劲度系数
springAnim.stiffness = 50.0f;
// 初始速度
springAnim.initialVelocity = 5.0f;
// 质量
springAnim.mass = 1.0f;
// 动画时间
springAnim.duration = springAnim.settlingDuration;
// 到达位置
springAnim.toValue = [NSValue valueWithCGPoint:CGPointMake(200, 200)];
// 添加动画
[self.colorLayer addAnimation:springAnim forKey:nil];
}
运行效果:

CAKeyframeAnimation
关键帧动画,同样通过keyPath
对应属性进行控制,但它可以通过values
或者path
进行多个阶段的控制。属性如下:
-
values
关键帧组成的数组,动画会依次显示其中的每一帧。 -
path
关键帧路径,动画进行的要素,优先级比values
高,但是只对CALayer
的anchorPoint
和position
起作用。 -
keyTimes
每一帧对应的时间,如果不设置,则各关键帧平分设定时间。 -
timingFunctions
每一帧对应的动画节奏。 -
calculationMode
动画的计算模式,系统提供了对应的几种模式。 -
tensionValues
动画张力控制。 -
continuityValues
动画连续性控制。 -
biasValues
动画偏差率控制。 -
rotationMode
动画沿路径旋转方式,系统提供了两种模式。
实例
CAKeyframeAnimation
使用values
数组可以设置多个关键帧,同时可以利用path
可以进行位置或者锚点的动画操作。
CAKeyframeAnimation *keyframeAnim = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation"];
keyframeAnim.duration = 0.5f;
keyframeAnim.values = @[@(-5/180.0*M_PI), @(5/180.0*M_PI), @(-5/180.0*M_PI)];
keyframeAnim.repeatCount = MAXFLOAT;
[self.colorLayer addAnimation:keyframeAnim forKey:@"keyFrameAnimation"];
运行效果:

UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 100, 250, 250)];
CAKeyframeAnimation *keyframeAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
keyframeAnim.path = path.CGPath;
keyframeAnim.duration = 2.0f;
keyframeAnim.repeatCount = MAXFLOAT;
[self.colorLayer addAnimation:keyframeAnim forKey:nil];
运行效果:

CATransition
转场动画,系统提供了很多酷炫效果。属性如下:
-
type
转场动画类型。CA_EXTERN NSString * const kCATransitionFade // 淡入淡出 CA_EXTERN NSString * const kCATransitionMoveIn // 新视图移动到旧视图上 CA_EXTERN NSString * const kCATransitionPush // 新视图推出旧视图 CA_EXTERN NSString * const kCATransitionReveal // 移开旧视图显示新视图
-
subtype
转场动画方向。CA_EXTERN NSString * const kCATransitionFromRight CA_EXTERN NSString * const kCATransitionFromLeft CA_EXTERN NSString * const kCATransitionFromTop CA_EXTERN NSString * const kCATransitionFromBottom
-
startProgress
动画起点进度(整体的百分比)。 -
endProgress
动画终点进度(整体的百分比)。 -
filter
自定义转场。
实例
- 创建转场动画
- 设置转场类型
type
,以及自类型subtype
(也就是转场方向,不是所有的效果都有子类型)及其他属性。 - 设置新的显示效果后,添加动画到图层。
CATransition *transition = [CATransition animation];
transition.type = kCATransitionFade;
transition.subtype = kCATransitionFromRight;
transition.duration = 1.0f;
self.colorLayer.contents = (id)[UIImage imageNamed:@"cat2"].CGImage;
[self.colorLayer addAnimation:transition forKey:nil];
运行效果:

CAAnimationGroup
在我们实际开发中,我们可能需要更加复杂的复合运动,那么需要给图层加多个动画,动画组也就应运而生,创建动画组也很简单,首先创建单个动画,然后将创建的多个动画添加到动画组,最后将动画组添加图层上就可以啦。不要认为动画组诗简单的动画的集合,因为其他动画有的属性很多动画组也有,比如timingFunction
,duration
,repeatCount
等,动画组和动画组的每一个元素都可以单独设置这些属性来实现一个不仅仅是单纯组合这么单纯的效果。
实例
- (void)testAnimationGroup {
CAKeyframeAnimation *keyframeAnim = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation"];
//每一个动画可以单独设置时间和重复次数,在动画组的时间基础上,控制单动画的效果
keyframeAnim.duration = 0.5f;
keyframeAnim.values = @[@(-5/180.0*M_PI), @(5/180.0*M_PI), @(-5/180.0*M_PI)];
keyframeAnim.repeatCount = MAXFLOAT;
CABasicAnimation *basicAnim = [CABasicAnimation animationWithKeyPath:@"position"];
//到达位置
basicAnim.toValue = [NSValue valueWithCGPoint:CGPointMake(300, 600)];
//动画时间
basicAnim.duration = 3;
//动画节奏
basicAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
CAAnimationGroup *animGroup = [CAAnimationGroup animation];
animGroup.animations = @[keyframeAnim, basicAnim];
//动画的表现时间和重复次数由动画组设置的决定
animGroup.duration = 3;
animGroup.repeatCount = MAXFLOAT;
animGroup.autoreverses = YES;
[self.colorLayer addAnimation:animGroup forKey:nil];
}
运行效果:

网友评论