Core Animation
话外:之前自己做的一个demo,用到了CABasicAnimation,觉得动画太神奇了,现在才开
始认真学习Core Animation!这篇文章只写了目前自己需要掌握的部分。
原博地址:CALayer与iOS动画 讲解及使用
1.关于Core Animation
CoreAnimation是苹果提供的一套基于绘图的动画框架,下图是官方文档中给出的体系结构。
Core Animation.png
从图中可以看出,最底层是图形硬件(GPU);上层是OpenGL和CoreGraphics,提供一些接口来访问GPU;再上层的CoreAnimation在此基础上封装了一套动画的API。最上面的UIKit属于应用层,处理与用户的交互。所以,学习CoreAnimation也会涉及一些图形学的知识,了解这些有助于我们更顺手的使用以及更高效的解决问题。
2.初识CALayer
CoreAnimation属于QuartzCore框架,Quartz原本是macOS的Darwin核心之上的绘图技术。在iOS中,我们所看到的视图UIView是通过QuartzCore中的CALayer显示出来的,我们讨论的动画效果也是加在这个CALayer上的。
- 下面主要的内容是:
CALayer(图层类)和CAAnimation(动画类)的内容和关系
以及他们实现的一个重要协议CAMediaTiming
CALayer图层类是CoreAnimation的基础,它提供了一套抽象概念。CALayer是整个图层类的基础,它是所有核心动画图层的父类。
1.CALayer
为什么UIView要加一层Layer来负责显示呢?我们知道QuartzCore是跨iOS和macOS平台的,而UIView属于UIKit是iOS开发使用的,在macOS中对应AppKit里的NSView。这是因为macOS是基于鼠标指针操作的系统,与iOS的多点触控有本质的区别。虽然iOS在交互上与macOS有所不同,但在显示层面却可以使用同一套技术。
每一个UIView都有个属性layer、默认为CALayer类型,也可以使用自定义的Layer
/* view的leyer,view是layer的代理 */
@property(nonatomic,readonly,strong) CALayer *layer;
可以想象我们看到的View其实都是它的layer,下面我们通过CALayer中的集合相关的属性来认识它:
-
bounds:图层的bounds是一个CGRect的值,指定图层的大小(bounds.size)和原点(bounds.origin)
-
position:指定图层的位置(相对于父图层而言)
-
anchorPoint:锚点指定了position在当前图层中的位置,坐标范围0~1。position点的值是相对于父图层的,而这个position到底位于当前图层的什么地方,是由锚点决定的。(默认在图层的中心,即锚点为(0.5,0.5) )。
划重点:设置完锚点后,都要在设置一下position的值 -
transform:指定图层的几何变换,类型为上篇说过的CATransform3D
这些属性的注释最后都有一句Animatable,就是说我们可以通过改变这些属性来实现动画。默认地,我们修改这些属性都会导致图层从旧值动画显示为新值,称为隐式动画。
注意到frame的注释里面是没有Animatable的。事实上,我们可以理解为图层的frame并不是一个真实的属性:当我们读取frame时,会根据图层position、bounds、anchorPoint和transform的值计算出它的frame;而当我们设置frame时,图层会根据anchorPoint改变position和bounds。也就是说frame本身并没有被保存。
图层不但给自己提供可视化的内容和管理动画,而且充当了其他图层的容器类,构建图层层次结构
图层树类似于UIView的层次结构,一个view实例拥有父视图(superView)和子视图(subView);同样一个layer也有父图层(superLayer)和子图层(subLayer)。我们可以直接在view的layer上添加子layer达到一些显示效果,但这些单独的layer无法像UIView那样进行交互响应。
2.CAMediaTiming
CAMediaTiming是CoreAnimation中一个非常重要的协议,CALayer和CAAnimation都实现了它来对时间进行管理。
协议定义了8个属性,通过它们来控制时间,这些属性大都见名知意:
@protocol CAMediaTiming
/* 开始的时间*/
@property CFTimeInterval beginTime;
/* 持续时间*/
@property CFTimeInterval duration;
@proterty float speed;
/* timeOffset时间的偏移量,用它可以实现动画的暂停、继续等效果*/
@proterty CFTimeInterval timeOffset;
/* 重复次数*/
@property float repeatCount;
@property CFTimeInterval repeatDuration;
/* autoreverses为true时时间结束后会原路返回,默认为false */
@property BOOL autoreverses;
/* fillMode填充模式,有4种,见下 */
@property(copy) NSString *fillMode;
@end
3.UIView动画
UIView提供了一系列UIViewAnimationWithBlocks,我们只需要把改变可动画属性的代码放在animations的block中即可实现动画效果,比如:
[UIView animateWithDuration:1 animations:^(void){
if (_testView.bounds.size.width > 150)
{
_testView.bounds = CGRectMake(0, 0, 100, 100);
}
else
{
_testView.bounds = CGRectMake(0, 0, 200, 200);
}
} completion:^(BOOL finished){
NSLog(@"%d",finished);
}];
其中 我自己有用到这个方法: 弹性动画(弹簧)
/* Performs `animations` using a timing curve described by the motion of a spring. When `dampingRatio` is 1, the
animation will smoothly decelerate to its final model values without oscillating. Damping ratios less than 1 will oscillate
more and more before coming to a complete stop. You can use the initial spring velocity to specify how fast the object at the
end of the simulated spring was moving before it was attached. It's a unit coordinate system, where 1 is defined as
travelling the total animation distance in a second. So if you're changing an object's position by 200pt in this animation, and
you want the animation to behave as if the object was moving at 100pt/s before the animation started, you'd pass 0.5. You'll
typically want to pass 0 for the velocity.
使用弹簧运动描述的时间曲线来执行“动画”。当“dampingRatio”为1时,动画将平稳地减速到最终的模型值,而不会出现振荡。阻尼比小于1会在完全停止前不断振荡。您可以
使用初始的弹簧速度来指定模拟弹簧的末端物体在被连接之前的移动速度。它是一个单位坐标系,其中1被定义为在一秒钟内移
动整个动画距离。如果你在这个动画中把一个对象的位置改变了200pt,你想让动画在动画开始前以100pt/s的速度运行,你就会
通过0。5。你通常要通过0来表示速度。*/
[UIView animateWithDuration:1.0 delay:0 usingSpringWithDamping:0.5 initialSpringVelocity:0.8 options:UIViewAnimationOptionCurveEaseOut animations:^{
// 添加发布按钮
[self addPubButton];
} completion:nil];
4.展示层(presentationLayer)和模型层(modelLayer)
我们知道UIView动画其实是layer层做的,而view是对layer的一层封装,我们对view的bounds等这些属性的操作其实都是对它所持有的layer进行操作,我们做一个简单的实验—在UIView动画的block中改变view的bounds后,分别查看下view和layer的bounds的实际值:
_testView.bounds = CGRectMake(0, 0, 100, 100);
[UIView animateWithDuration:1 animations:^(void){
_testView.bounds = CGRectMake(0, 0, 200, 200);
} completion:nil];
赋值完成后我们分别打印view,layer的bounds:
(lldb) po _textView.bounds
(origin = (x = 0, y = 0), size = (width = 200, height = 200))
(lldb) po _textView.layer.bounds
(origin = (x = 0, y = 0), size = (width = 200, height = 200))
都已经变成了(200,200),这是肯定的,之前已经验证过set view的bounds实际上就是set 它的layer的bounds。可动画不是layer实现的么?layer也已经到达终点了,它是怎么将动画展示出来的呢?
这里就要提到CALayer的两个实例方法presentationLayer和modelLayer:
@interface CALayer : NSObject <NSCoding, CAMediaTiming>
/* 以下参考官方api注释 */
/* presentationLayer
* 返回一个layer的拷贝,如果有任何活动动画时,包含当前状态的所有layer属性
* 实际上是逼近当前状态的近似值。
* 尝试以任何方式修改返回的结果都是未定义的。
* 返回值的sublayers 、mask、superlayer是当前layer的这些属性的presentationLayer
*/
- (nullable instancetype)presentationLayer;
/* modelLayer
* 对presentationLayer调用,返回当前模型值。
* 对非presentationLayer调用,返回本身。
* 在生成表示层的事务完成后调用此方法的结果未定义。
*/
- (instancetype)modelLayer;
从注释不难看出,这个presentationLayer即是我们看到的屏幕上展示的状态,而modelLayer就是我们设置完立即生效的真实状态
总结
- 到这里,CALayer动画的原理基本清晰了,当有动画加入时,presentationLayer会不断的(从按某种插值或逼近得到的动画路径上)取值来进行展示,当动画结束被移除时则取modelLayer的状态展示。这也是为什么我们用CABasicAnimation时,设定当前值为fromValue时动画执行结束又会回到起点的原因,实际上动画结束并不是回到起点而是到了modelLayer的位置。
虽然我们可以使用fillMode控制它结束时保持状态,但这种方法在动画执行完之后并没有将动画从渲染树中移除(因为我们需要设置animation.removedOnCompletion = NO才能让fillMode生效)。如果我们想让动画停在终点,更合理的办法是一开始就将layer设置成终点状态,其实前文提到的UIView的block动画就是这么做的。 - UIView持有一个CALayer负责展示,view是这个layer的delegate。改变view的属性实际上是在改变它持有的layer的属性,layer属性发生改变时会调用代理方法actionForLayer: forKey: 来得知此次变化是否需要动画。对同一个属性叠加动画会从当前展示状态开始叠加并最终停在modelLayer的真实位置。
- CALayer内部控制两个属性presentationLayer和modelLayer,modelLayer为当前layer真实的状态,presentationLayer为当前layer在屏幕上展示的状态。presentationLayer会在每次屏幕刷新时更新状态,如果有动画则根据动画获取当前状态进行绘制,动画移除后则取modelLayer的状态。
CALayer和UIView
CALayer和UIView的区别
1.UIView是UIKit的(只能iOS使用),CALayer是QuartzCore的(iOS和mac os通用)
2.UIView继承UIResponder,CALayer继承NSObject,UIView比CALayer多了一个事件处理的功能,也就是说,CALayer不能处理用户的触摸事件,而UIView可以
3.UIView来自CALayer,是CALayer的高层实现和封装,UIView的所有特性来源于CALayer支持
4.CABasicAnimation,CAAnimation,CAKeyframeAnimation等动画类都需要加到CALayer上
其实UIView之所以能显示在屏幕上,完全是因为它内部的一个图层,在创建UIView对象时,UIView内部会自动创建一个图层(即CALayer对象),通过UIView的layer属性可以访问这个层。
@property(nonatomic,readonly,retain) CALayer *layer;
当UIView需要显示到屏幕上时,会调用drawRect:方法进行绘图,并且会将所有内容绘制在自己的图层上,绘图完毕后,系统会将图层拷贝到屏幕上,于是就完成了UIView的显示
换句话说,UIView本身不具备显示的功能,是它内部的层才有显示功能
CoreAnimation
CAAnimation:核心动画的基础类,不能直接使用,负责动画运行时间、速度的控制,本身实现了CAMediaTiming协议。
CAPropertyAnimation:属性动画的基类(通过属性进行动画设置,注意是可动画属性),不能直接使用。
CAAnimationGroup:动画组,动画组是一种组合模式设计,可以通过组合动画组来进行所有动画行为的统一控制,组中的所有动画效果可以并发执行。
CATransition:转场动画,通过滤镜进行动画效果设置。
CABasicAnimation:基础动画,通过属性修改进行动画参数控制,只有初始状态和结束状态。
CAKeyframeAnimation:关键帧动画,同样是通过属性进行动画参数控制,但是同基础动画不同的是它可以有多个状态控制。