前言:
Core Animation是一套包含图形绘制、投影、动画的Objective-C类集合,该框架包含在QuartzCore.framework
中,它因为被用于处理更为强大的平滑的转场效果而引入OS X Leopard和iOS而出名。
CPU作为电脑的处理核心,负责处理各种各样的数据,什么都可以干。而GPU作为一个专门为图形高并发计算
量身定做的处理单元,能同时更新所有的像素
,并把结果显示到显示器上,在图形处理方面具有相当的专业性,而且还具有高效的特点。它的出现使得CPU从大量的图形数据处理中解放出来。
上图中的OpenGL ES是个C语言写的非常底层的图形处理框架,是个移动设备上绘制2D和3D计算机图形的标准开源库,广泛地被用在游戏的图形绘制上,负责直接驱动GPU,效率非常高,缺点是使用起来异常复杂。
Core Animation是对OpenGL ES的Objective-C封装,具有与OpenGL ES几乎等价的高性能,却隐藏了OpenGL ES的复杂性。
我们经常说到的硬件加速
其实是指OpenGL
,Core Animation/UIKit基于GPU之上对计算机图形合成以及绘制的实现,直到目前为止,IOS上的硬件加速能力还是大大领先与Android,后者由于依赖CPU的绘制,绝大多数的动画实现都会让人感觉明显的卡顿。
框架下各类的关系图:
在介绍框架下各大类之前,首先需要了解一下各大图层类的父类CALayer,也是CoreAnimation
的基础,如果有兴趣也可以看看CALayer的一些专用图层子类。
动画是CoreAnimation
的一个显著特性,而iOS的动画也分显式和隐式的。首先我们来介绍一下隐式动画:
<一>:隐式动画
当你改变CALayer的一个可做动画的属性,它并不能立刻在屏幕上体现出来。相反,它是从先前的值平滑过渡到新的值。这一切都是默认的行为,你不需要做额外的操作。
之所以叫隐式
是因为我们并没有指定任何动画的类型。我们仅仅改变了一个属性,然后Core Animation来决定如何并且何时去做动画。
事务(CATransaction):
事务实际上是Core Animation用来包含一系列属性动画集合的机制,任何用指定事务去改变可以做动画的图层属性都不会立刻发生变化,而是当事务一旦提交的时候开始用一个动画过渡到新值。
Core Animation在每个run loop周期中自动开始一次新的事务(run loop是iOS负责收集用户输入,处理定时器或者网络事件并且重新绘制屏幕的东西),即使你不显式的用[CATransaction begin]开始一次事务,任何在一次run loop循环中属性的改变都会被集中起来,然后做一次 0.25秒 的动画。
core animation必须在一个transaction里执行,在transaction里,layer tree把相应的动画交给presentation tree去展示。
-
+begin
、commit
:入栈和出栈。 -
+setAnimationDuration:
:设置当前事务的动画时间。 -
+animationDuration:
获取当前事务的动画时间。 -
+setDisableActions:
:可以用来对所有属性打开或关闭隐式动画。
http://blog.csdn.net/robincui2011/article/details/9464781
http://www.cnblogs.com/bandy/archive/2012/03/26/2418165.html
隐式动画和隐式事务:
隐式动画通过隐式事务实现动画 。(除显式事务外,任何对于CALayer属性的修改,都是隐式事务.这样的事务会在run-loop中被提交)
layer.backgroundColor = (layer.backgroundColor == redColor) ? blueColor : redColor;
layer.opacity = 1.0f;
layer.position = CGPointMake(layer.position.x +10, layer.position.y);
区分显式动画和显式事务:
显式动画有多种实现方式,显式事务是一种实现显式动画的方式。 ( 通过明确的调用begin,commit来提交动画 ) .
通过给 CATransaction 类发送一个 begin 消息来创建一个显式事务,修改完成之后发送 comit 消息。显式事务在同时设置多个图层的属性的时候(例如当布局多个图层的时候),暂时的禁用图层的行为,或者暂时修改动画的时间的时候非常有用 .
[CATransaction begin];
[CATransaction setValue:(_swich.on?(id)kCFBooleanTrue:(id)kCFBooleanFalse) forKey:kCATransactionDisableActions];
[self setLayerBC];
[CATransaction commit];
设置动画是否显示(禁用图层行为)(方法控制区直到函数的结束):
//默认为YES不显示
[CATransaction setDisableActions:NO];
暂时禁用图层行为:
可以在修改图层属性值的时候通过设置事务的 kCATransactionDisableActions值为 YES 来暂时禁用图层的行为。在事务范围
所作的任何更改也不会因此而发生的动画。
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
[aLayer removeFromSuperlayer];
[CATransaction commit];
重载隐式动画的时间:
以暂时改变响应改变图层属性的动画的时间,通过设置事务的kCATransactionAnimationDuration 键的值为新的时间。事务范围内所产生的任何动画都会使用该新设置的时间值而不是他们原有的值。
[CATransaction setValue:[NSNumber numberWithFloat:10.0f]
forKey:kCATransactionAnimationDuration];
简单使用:(如果对UIView关联的图层做如下动画是没有过渡效果的,因为UIView把它关联的图层的可动画属性关闭了)
- (IBAction)changeColor
{
[CATransaction begin];
[CATransaction setAnimationDuration:1.0];
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;
//完成块动画,类似于UIView的completionBlock
[CATransaction setCompletionBlock:^{
//rotate the layer 90 degrees
CGAffineTransform transform = self.colorLayer.affineTransform;
transform = CGAffineTransformRotate(transform, M_PI_2);
self.colorLayer.affineTransform = transform;
}];
[CATransaction commit];
}
<注>:UIView在+beginAnimations:context:和+commitAnimations之间所有视图或者图层属性的改变而做的动画都是由于设置了CATransaction的原因。CATransaction的+begin和+commit方法在+animateWithDuration:animations:内部自动调用,可以避免开发者由于对+begin和+commit匹配的失误造成的风险。
呈现与模型:
- 呈现图层:每个图层属性的显示值都被存储在一个叫做呈现图层的独立图层当中,他可以通过-presentationLayer方法来访问,可以通过呈现图层的值来获取
当前屏幕上真正显示出来的值
。 - –modelLayer:在呈现图层上调用–modelLayer将会返回它正在呈现所依赖的CALayer。通常在一个图层上调用-modelLayer会返回–self
<二>:显式动画
能够对一些属性做指定的自定义动画,或者创建非线性动画,比如沿着任意一条曲线移动。
使用隐式动画会直接改变layer的属性值,而使用显式动画,动画结束时不影响动画前的layer属性值。
属性动画(CAPropertyAnimation)
内部实现:
当CALayer的属性被修改时候,它会调用-actionForKey:(CALayer拿这个结果去对先前和当前的值做动画。)方法,传递属性的名称。剩下的操作都在CALayer的头文件中有详细的说明,实质上是如下几步:
检查是否有委托及委托方法是否实现 ==> 检查包含属性名称对应的action字典 ==> 在style字典里搜索属性名 ==> 调用-defaultActionForKey:方法。
图层首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法。如果有,直接调用并返回结果。
如果没有委托,或者委托没有实现-actionForLayer:forKey方法,图层接着检查包含属性名称对应行为映射的actions字典。
如果actions字典没有包含对应的属性,那么图层接着在它的style字典接着搜索属性名。
最后,如果在style里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的-defaultActionForKey:方法。
当更新属性的时候,我们需要设置一个新的事务,并且禁用图层行为
。否则动画会发生两次,一个是因为显式的CABasicAnimation,另一次是因为隐式动画
- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag
{
[CATransaction begin];
[CATransaction setDisableActions:YES];//禁用隐式动画的过度效果。
self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;
[CATransaction commit];
}
-
keyPath
: CAPropertyAnimation 属性。
基础动画(CABasicAnimation):
继承自CAPropertyAnimation。
参考链接
基础属性介绍:
防止动画回到初始状态的设置:
我们给一个视图添加layer动画时,真正移动并不是我们的视图本身,而是 presentation layer (呈现图层) 的一个缓存。动画开始时 presentation layer开始移动,原始layer(model layer)隐藏,动画结束时,presentation layer从屏幕上移除,原始layer显示
transformAnima.removedOnCompletion = NO;
transformAnima.fillMode = kCAFillModeForwards;//将layer的状态设置为最终的属性。
<注>:CABasicAnimation的实例对象只是一个数据模型,与绑定到哪一个layer上无关。并且用addAnimation:forKey:
方法时是对CABasicAniamtion对象进行了copy操作的。
避免循环引用:
CABasicAnimation的代理是强指针引用,在设置代理的时候会造成循环引用
1、创建一个代理类实现其回调。
self.animation.delegate = [[AnimationDelegate alloc]init];
2、采用虚拟类NSProxy(该类可实现多继承),可用YYKit
中的YYWeakProxy
类:
self.animation.delegate = [YYWeakProxy proxyWithTarget:self];
区分不同动画:
此处不能使用存储属性作比较,因为委托传递的参数是对原始值的一个深拷贝,所以两者并不是一个值。
我们在addAnimation:forKey:
中可设置key值,所以可根据key值判断:
1、对比动画:
[anim isEqual:[self.imageView.layer animationForKey:@"Animation"]]
2、对比key:
[[anim valueForKey:@"AnimationKey"]isEqualToString:@"PositionAnima"]
组动画 (CAAnimationGroup)
CAAnimationGroup是CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行
-
animations
:用来保存一组动画对象的NSArray
默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间。
栗子:
CABasicAnimation *positionAnima = [CABasicAnimation animationWithKeyPath:@"position.y"];
positionAnima.fromValue = @(self.imageView.center.y);
positionAnima.toValue = @(self.imageView.center.y-30);
positionAnima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
CABasicAnimation *transformAnima = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
transformAnima.fromValue = @(0);
transformAnima.toValue = @(M_PI);
transformAnima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
CAAnimationGroup *animaGroup = [CAAnimationGroup animation];
animaGroup.duration = 2.0f;
animaGroup.fillMode = kCAFillModeForwards;
animaGroup.removedOnCompletion = NO;
animaGroup.animations = @[positionAnima,transformAnima];
[self.imageView.layer addAnimation:animaGroup forKey:@"Animation"];
关键帧动画(CAKeyFrameAnimation):
相比CABasicAnimation的从
fromValue
到toValue
的过渡动画,CAKeyFrameAnimation可以用一个集合Values
保存关键帧值。
-
Values
:指定要用于动画的关键帧值的对象数组。 -
path
: 基于点的属性的路径。 -
keyTimes
:NSNumber对象的可选数组,用于定义应用给定关键帧段的时间,与Values数组意义对应。范围:0~1。
关键帧动画:(CAKeyframeAnimation)
-
rotationMode
:设置它为常量kCAAnimationRotateAuto,图层将会根据曲线的切线自动旋转。 -
timingFunctions
:NSArray类型,我们可以用它来对每次动画的步骤指定不同的计时函数。但是指定函数的个数一定要等于keyframes数组的元素个数减一,因为它是描述每一帧之间动画速度的函数。
Values方式:
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position";
NSValue *value1=[NSValue valueWithCGPoint:CGPointMake(100, 100)];
NSValue *value2=[NSValue valueWithCGPoint:CGPointMake(200, 100)];
NSValue *value3=[NSValue valueWithCGPoint:CGPointMake(200, 200)];
NSValue *value4=[NSValue valueWithCGPoint:CGPointMake(100, 200)];
NSValue *value5=[NSValue valueWithCGPoint:CGPointMake(100, 100)];
animation.values=@[value1,value2,value3,value4,value5];
animation.repeatCount=MAXFLOAT;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
animation.duration = 4.0f;
animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.delegate=self;
//指定时间点。
animation.keyTimes = @[@(0.0),@(0.3),@(0.6),@(0.9),@(1)];
[self.my_View.layer addAnimation:animation forKey:nil];
path方式:
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position";
CGMutablePathRef path=CGPathCreateMutable();
//绘制椭圆 椭圆距离左150,上100,横向直径100,竖向直径300
CGPathAddEllipseInRect(path, NULL, CGRectMake(150, 100, 100, 300));
animation.path=path;
CGPathRelease(path);
animation.repeatCount=MAXFLOAT;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
animation.duration = 4.0f;
animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.delegate=self;
[self.my_pathView.layer addAnimation:animation forKey:nil];
过渡动画(CATransition):
过渡动画基础的原则就是对原始的图层外观截图,然后添加一段动画,平滑过渡到图层改变之后那个截图的效果。
CATransition 通常用于通过CALayer控制View子控件的过渡动画,比如删除控件,添加控件及切换子控件等。
CATransition有一个type和subtype来标识变换效果,type是一个NSString类型。default:kCATransitionFade。
type
define 定义的常量:
kCATransitionFade 交叉淡化过渡
kCATransitionMoveIn 新视图移到旧视图上面
kCATransitionPush 新视图把旧视图推出去
kCATransitionReveal 将旧视图揭开,显示下面的新视图
私有方法:
cube 立方体旋转
suckEffect 收缩动画
oglFlip 翻转
rippleEffect 水波动画
pageCurl 页面揭开
pageUnCurl 放下页面
cemeraIrisHollowOpen 镜头打开
cameraIrisHollowClose 镜头关闭
subtype
用来控制动画的方向:
kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromTop
kCATransitionFromBottom
对指定的图层一次只能使用一次CATransition
,因此,无论你对动画的键设置什么值,过渡动画都会对它的键设置成“transition”,也就是常量kCATransition
。
栗子:
if (self.my_transition==nil) {
self.my_transition=[[UIView alloc]initWithFrame:CGRectMake(0,[UIScreen mainScreen].bounds.size.height-250,[UIScreen mainScreen].bounds.size.width,250)];//这里是动画最后的位置
self.my_transition.backgroundColor=[UIColor redColor];
[self.view addSubview:self.my_transition];
}
CATransition *animation = [CATransition animation];
animation.duration = 1;
animation.timingFunction = UIViewAnimationCurveEaseInOut;
animation.fillMode = kCAFillModeForwards;
animation.type = kCATransitionMoveIn;//
animation.subtype = kCATransitionFromTop;//向上。
//添加动画
[self.my_transition.layer addAnimation:animation forKey:nil];
CATransision可以对图层任何变化平滑过渡的事实使得它成为那些不好做动画的属性图层行为的理想候选,但是对于视图关联的图层,或者是其他隐式动画的行为,这个特性依然是被禁用的.
使用UIKit中来做动画:
- (IBAction)switchImage
{
[UIView transitionWithView:self.imageView duration:1.0
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
//cycle to next image
UIImage *currentImage = self.imageView.image;
NSUInteger index = [self.images indexOfObject:currentImage];
index = (index + 1) % [self.images count];
self.imageView.image = self.images[index];
}
completion:NULL];
}
在动画过程中取消动画:
动画一旦被移除,图层的外观就立刻更新到当前的模型图层的值。
- 移除指定动画:
- (void)removeAnimationForKey:(NSString *)key;
- 移除所有动画:
- (void)removeAllAnimations;
<三>图层时间
动画的发生是需要持续一段时间的,所以计时对整个概念来说至关重要。
CAMediaTiming协议:
CAMediaTiming协议定义了在一段动画内用来控制逝去时间的属性的集合,CALayer和CAAnimation都实现了这个协议,所以时间可以被任意基于一个图层或者一段动画的类控制。
duration
和repeatCount
默认都是0。但这不意味着动画时长为0秒,或者0次,这里的0仅仅代表了“默认”,也就是0.25秒和1次.timeOffset
:timeOffset
只是让动画快进到某一点,例如,对于一个持续1秒的动画来说,设置timeOffset为0.5意味着动画将从一半的地方开始。fillMode
:设置当动画开始或者动画结束时的值。default:kCAFillModeRemoved
,要把removeOnCompletion
设置为NO。
全局时间:
对
CALayer
或者CAGroupAnimation
调整duration
和repeatCount
/repeatDuration
属性并不会影响到子动画。但是beginTime
,timeOffset
和speed
属性将会影响到子动画。
马赫时间:实际上是iOS和Mac OS系统内核的命名,马赫时间在设备上所有进程都是全局的--但是在不同设备上并不是全局的。当设备休眠的时候马赫时间会暂停,也就是所有的CAAnimations
(基于马赫时间)同样也会暂停。
CFTimeInterval time = CACurrentMediaTime();
本地时间:是根据父图层/动画层级关系中的beginTime
,timeOffset
和speed
属性计算。就和转换不同图层之间坐标关系一样,CALayer
同样也提供了方法来转换不同图层之间的本地时间。如下:
- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(CALayer *)l;
- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(CALayer *)l;
暂停、快进和倒回:
给图层添加一个CAAnimation
实际上是给动画对象做了一个不可改变的拷贝,所以对原始动画对象属性的改变对真实的动画并没有作用。相反,直接用-animationForKey:
来检索图层正在进行的动画可以返回正确的动画对象,但是修改它的属性将会抛出异常。
一个简单的方法是可以利用CAMediaTiming
来暂停图层本身。如果把图层的speed
设置成0,它会暂停任何添加到图层上的动画。类似的,设置speed
大于1.0将会快进,设置成一个负值将会倒回动画。
通过增加主窗口图层的speed
,可以暂停整个应用程序的动画。这对UI自动化提供了好处,我们可以加速所有的视图动画来进行自动化测试(注意对于在主窗口之外的视图并不会被影响,比如UIAlertview
)。可以在app delegate设置如下进行验证:
self.window.layer.speed = 100;
手动动画:
timeOffset
一个很有用的功能在于你可以它可以让你手动控制动画进程,通过设置speed
为0,可以禁用动画的自动播放,然后来使用timeOffset
来来回显示动画序列。这可以使得运用手势来手动控制动画变得很简单。
缓冲(CAMediaTimingFunction)
动画速度:
velocity = change / time
对于这种恒定速度的动画我们称之为“线性步调”.
CAMediaTimingFunction:
缓冲方程式的使用(同UIView
的options
属性):
- 显式:设置
CAAnimation
的timingFunction
为CAMediaTimingFunction
的一个对象。
- 隐式:可以使用
CATransaction
的+setAnimationTimingFunction:
方法。
- 其它:
+timingFunctionWithName:
,传入以下常量。
kCAMediaTimingFunctionLinear //立即加速并且保持匀速到达终点的场景会有意义
kCAMediaTimingFunctionEaseIn //慢慢加速然后突然停止
kCAMediaTimingFunctionEaseOut //全速开始,然后慢慢减速停止
kCAMediaTimingFunctionEaseInEaseOut //慢慢加速然后再慢慢减速
kCAMediaTimingFunctionDefault //和kCAMediaTimingFunctionEaseInEaseOut很类似,但是加速和减速的过程都稍微有些慢
隐式动画关于缓冲方程式的使用:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//configure the transaction
[CATransaction begin];
[CATransaction setAnimationDuration:1.0];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
//set the position
self.colorLayer.position = [[touches anyObject] locationInView:self.view];
//commit transaction
[CATransaction commit];
}
自定义缓冲函数:
CAMediaTimingFunction
的构造函数:+functionWithControlPoints::::
- 通过
CAMediaTimingFunction
中-getControlPointAtIndex:values:
可以用来检索曲线的点,当知道曲线起始点和终止点,我们便可以通过这个方法将控制点找出来。
Core Animation 的插值机制:
value = (endValue – startValue) × time + startValue;
基于定时器的动画
定时帧:
NSTimer:iOS上的每个线程都管理了一个NSRunloop
,就是通过一个循环来完成一些任务列表。
任务包括:
- 处理触摸事件
- 发送和接受网络数据包
- 执行使用gcd的代码
- 处理计时器行为
- 屏幕重绘
当你设置一个NSTimer
,他会被插入到当前任务列表中,然后直到指定时间过去之后才会被执行。但是何时启动定时器并没有一个时间上限,而且它只会在列表中上一个任务完成之后开始执行。这通常会导致有几毫秒的延迟,但是如果上一个任务过了很久才完成就会导致延迟很长一段时间。
优化:
- 我们可以用
CADisplayLink
让更新频率严格控制在每次屏幕刷新之后。 - 基于真实帧的持续时间而不是假设的更新频率来做动画。
- 调整动画计时器的
run loop
模式,这样就不会被别的事件干扰。
CADisplayLink:
frameInterval
:整型,指定了间隔了多少帧后才执行。(2,表示间隔2帧后执行,也就是每秒执行60/2 次)。
优点: 会保证帧率足够连续,使得动画看起来更加平滑。
缺点: 一些失去控制的离散的任务或者事件(例如资源紧张的后台程序)可能会导致动画偶尔地丢帧。丢帧后直接忽略,然后下一次更新时继续运行。
可以同时对CADisplayLink
指定多个run loop模式,于是我们可以同时加入NSDefaultRunLoopMode
和UITrackingRunLoopMode
来保证它不会被滑动打断,也不会被其他UIKit控件动画影响性能,像这样:
self.timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(step:)];
[self.timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[self.timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode];
全文基于对iOS Core Animation的译文的理解,接下来我会针对部分细节功能作更多详细实例补充,如果喜欢请记得关注我。谢谢您的阅读~