CoreAnimation
本次教程分为两个部分,分别为理论和实战,实战部分,主要就是一些代码,对理论部分的解析,下面让我们进入下面我们的第一部分CoreAnimation With Swift 理论
CALayer基础
UIView
之所以能显示在屏幕上,完全是因为它内部的一个图层.当UIView
需要显示到屏幕上时,会调用drawRect:
方法进行绘图,并且会将所有内容绘制在自己的图层上,绘图完毕后,系统会将图层拷贝到屏幕上,于是就完成了UIView
的显示。CALayer
是用来管理位图的state,以及如何被渲染出来。
CALayer基本属性
CALayer
中anchorpoint
是图层的关键,anchorpoint
的位置发生变化,包括CALayer
的position
等属性相应的就会发生变化。anchorpoint
是用单位坐标的(x,y轴都是从0到1,默认的anchorpoint
的坐标为(0.5,0.5))
position:CGPoint
用来设置CALayer在父层中的位置
anchorPoint:CGPoint
决定着CALayer身上的哪个点会在position属性所指的位置
contentsGravity
当图片大小不是适合当前view的时候,你可能会使用view.contentMode
来设置缩放和位置,其实他是通过控制CALayer
的contentGravity
属性来实现的
contentsScale
由于layer
不能区分分辨率,contentScale
用来定义像素尺寸到layer尺寸的拉伸比率,默认值是1
contentsRect
contentsRect
使用的是单位坐标,表示的是需要显示的内容的尺寸
contentsCenter
contentsCenter:CGRect
定义了layer内部一个可以被拉伸的区域,外部则是一个固定的边界。这个属性将一个layer分为9个部分。根据contentsGravity
的设定,拉伸相应地区域。可以参考微信聊天记录上那个气泡
layer的视觉效果
圆角
CALayer
有一个cornerRadius
属性用来制作layer的圆角,默认情况下该属性只作用于backgroundColor
,而对于sublayer和背景图都没有效果,但是如果配合masksToBounds=YES
的设置,可以对layer内所有元素生效。
边框
CALayer
通过borderWidth
和borderColor
两个属性的组合来定义border的宽度和颜色
阴影
通过设置shadowOpacity
属性为一个大于0的值,可以给任意layer添加一个背景阴影。取值范围是0.0到1.0,表示全透明到非透明。同时还可以通过shadowColor
(默认为黑)、shadowOffset
(距离和方向)和shadowRadius
(模糊度)三个属性来修改阴影的外观。不同于border围绕在layer的bounds,shadow是围绕在真正的内容外的。可以定义shadowPath
来自定义阴影路径
遮罩(Masking)
mask
属性接受另外一个CALayer
值,可以被用作将当前layer裁切成给定layer的轮廓。masksToBounds
则是让子视图不超过父视图的范围
Opacity
这里要说得是当子图层和父图层都设置了Opacity
的时候(比如父类为0.5,子类为0.5 ,那么实际上子类的透明度为0.5*0.5+0.5(父类)=0.75).而下面是你设置透明度和alpha等需要注意的
1.opaque 直接不再绘制这层layer下面的layer(效率高)
2.alpha会影响在layer上视图的透明度
- 在app的Info.plist中添加UIViewGroupOpacity=YES。这会作用于当前app下的所有范围,可能还有点小的性能浪费。
- 通过
CALayer
的shouldRasterize=YES
,将layer及其子layer全部打散成一个平面的图形,再使Opacity
生效就可以达到我们想要的效果了。由于rasterizationScale
默认等于1.0,会使得高清屏幕下图片像素化,所以我们还要同时设置一下:layer.rasterizationScale = UIScreen.mainScreen().scale
Transforms
UIView
的transform
属性类型是GAffineTransform
,它用于对view进行二维空间下的旋转、拉伸或变形。其实他就是对CALayer
的封装。CALayer
同时也有一个transform
属性,它的类型是CATransform3D
(CATransform3D
是一个4x4的矩阵,从而支持了对于垂直屏幕的z轴的变换效果。其中m34属性通常被用来计算x和y值的缩放比例,默认值是0)。CALayer
实现仿射变化的属性是affineTransform
(UIView
就是对这个的封装)。CATransform3D
的定义和它各个参数的作用如下。
struct CATransform3D {
m11(x缩放), m12(y切变),m13(旋转), m14();
m21(x切变), m22(y缩放), m23(), m24();
m31(旋转), m32( ), m33(),
m34(透视效果,要操作的这个对象要有旋转的角度,否则没有效果。正直/负值都有意义)
m41(x平移), m42(y平移), m43(z平移), m44();
};
CATransform3D相关的函数
CATransform3D CATransform3DTranslate (CATransform3D t, CGFloat tx, CGFloat ty, CGFloat tz);
t:就可以理解为:函数的叠加,效果的叠加。
CATransform3D CATransform3DMakeScale (CGFloat sx, CGFloat sy, CGFloat sz);
sx:X轴缩放,代表一个缩放比例,一般都是 0 --- 1 之间的数字。sy:Y轴缩放。
sz:整体比例变换时,也就是m11(sx)== m22(sy)时,若m33(sz)>1,图形整体缩小,若0<1,图形整体放大,若m33(sz)<0,发生关于原点的对称等比变换。
CATransform3D CATransform3DMakeRotation (CGFloat angle, CGFloat x, CGFloat y, CGFloat z);
旋转效果。angle:旋转的弧度,所以要把角度转换成弧度:角度 * M_PI / 180。
x:向X轴方向旋转。值范围-1 --- 1之间y:向Y轴方向旋转。值范围-1 --- 1之间
z:向Z轴方向旋转。值范围-1 --- 1之间
CALayer的子类
CAGradientLayer
实现CALayer
的渐变色
// 渐变图层
let gradientL = CAGradientLayer()
gradientL.frame = bottomView.bounds;
gradientL.opacity = 0;
gradientL.colors = @[UIColor.clearColor().CGColor,UIColor blackColor().CGColor];
CAReplicatorLayer
CAReplicatorLayer
复制图层,把子图层放在CAReplicatorLayer
中,,可以把图层里面所有子层复制.这个图层用于,有许多相同的子视图做相同的动画效果的时候,这个可以复制这些子视图,并且为这些子视图加上延迟等效果
let repL = CAReplicatorLayer()
repL.frame = _lightView.bounds;
_lightView.layer.addSublayer(repL)
// 子图层
let layer = CALayer()
layer.anchorPoint = CGPointMake(0.5, 1)
layer.position = CGPointMake(15, _lightView.bounds.size.height)
layer.bounds = CGRectMake(0, 0, 30, 150)
repL.addSublayer(layer)
let anim = CABasicAnimation()
anim.keyPath = "transform.scale.y"
anim.toValue = 0.1
anim.duration = 0.5
anim.repeatCount = MAXFLOAT;
// 设置动画反转
anim.autoreverses = YES;
layer.addAnimation(anim,forKey:nil)
// 复制层中子层总数
// instanceCount:表示复制层里面有多少个子层,包括原始层
repL.instanceCount = 3;
// 设置复制子层偏移量,不包括原始层,相对于原始层x偏移,instanceTransform决定如何排序
repL.instanceTransform = CATransform3DMakeTranslation(45, 0, 0);
// 设置复制层动画延迟时间
repL.instanceDelay = 0.1;
// 如果设置了原始层背景色,就不需要设置这个属性
repL.instanceColor = UIColor.greenColor().CGColor
repL.instanceGreenOffset = -0.3;
UIView和CALayer的选择
其实,对比CALayer
,UIView
多了一个事件处理的功能。也就是说,CALayer
不能处理用户的触摸事件,而UIView
可以.所以,如果显示出来的东西需要跟用户进行交互的话,用UIView
;如果不需要跟用户进行交互,用UIView
或者CALayer
都可以当然,CALayer
的性能会高一些,因为它少了事件处理的功能,更加轻量级。虽然CALayer
不感知responder chain,但是他通过提供-containPoint:
和-hitTest:
两个方法来帮助你定位事件所在layer。
我们为什么要了解CALayer
的存在,因为他有一些UIView
不能做的事情:
- 阴影、圆角、多彩的border
- 3D变化和定位
- 非矩形的边界
- alpha遮罩
- 多步非线性动画
layer的绘制
在UIView的子类中可以通过实现-drawRect:
方法来自定义绘图,CALayer
的自定义绘图则通过layer的delegate
对象(CALayerDelegate
协议)的以下两个方法来实现:
// 方法1 需要手动调用layer.display()方法来调用
func displayLayer(_ layer: CALayer!);
// 方法2. 如果方法1没有实现,则调方法2。需要手动调用layer.display()方法来调用
func drawLayer(_ layer: CALayer!, inContext ctx,: CGContext )
layer tree
CoreAnimation
是一个合成引擎,它的工作是尽可能快的将不同的可视化的内容合成到屏幕上。这个可视化内容是不同的层(layer),构成一个叫做层树(layer tree)的层次结构。层树主要由三个部分组成:
- 模型层树:app交互的地方,存储目标值的地方
- 外观层树:展示值得地方(从origin到target),在这一层获取动画过程中的当前值,这一层通过
presentationLayer()
方法获取。这层就是所看到的动画,但是实际上控件上的视图的frame之类的属性没有发生变化。 - 渲染层树:私有的方法
隐式动画
每一个UIView
内部都默认关联着一个CALayer
,我们可用称这个Layer为Root Layer(根层)。所有的非Root Layer(也就是手动创建的CALayer对象),都存在着隐式动画,也就是说当对非Root Layer的部分属性进行修改时,默认会自动产生一些动画效果而这些属性称为Animatable Properties
(可动画属性)常见的Animatable Properties:
-
bounds
:用于设置CALayer
的宽度和高度。修改这个属性会产生缩放动画 -
backgroundColor
:用于设置CALayer
的背景色。修改这个属性会产生背景色的渐变动画 -
position
:用于设置CALayer
的位置。修改这个属性会产生平移动画
当然这个隐式的动画可以通过动画事务(CATransaction)关闭,它负责成批的把多个图层树的修改作为一个原子更新到渲染树。
核心动画
核心动画继承结构
核心动画的继承关系如下
从上图中可以看到核心动画中所有类都遵守
CAMediaTiming
。CAAnaimation是所有动画类的父类,是个抽象类,不具备动画效果.CAAnimationGroup
和CATransition
才有动画效果,CAAnimationGroup
是个动画组,可以同时进行缩放,旋转。CATransition
是转场动画,界面之间跳转都可以用转场动画。CAPropertyAnimation
也是个抽象类,本身不具备动画效果,只有子类才有.CABasicAnimation
基本动画,做一些简单效果CAKeyframeAnimation
帧动画,做一些连续的流畅的动画.给一个图层添加动画的步骤为如下
- 创建
CALayer
- 初始化一个
CAAnimation
对象,并设置一些动画相关属性 - 通过调用
CALayer
的addAnimation:forKey:
方法,增加CAAnimation
对象到CALayer
中,这样就能开始执行动画了 - 通过调用
CALayer
的removeAnimationForKey:
方法可以停止CALayer
中的动画
CAAnimation的属性介绍
CAAnimation
是所有动画对象的父类,负责控制动画的持续时间和速度,是个抽象类,不能直接使用.下面需要对它的一些属性的介绍
CAMediaTiming
协议的属性
- duration:动画的持续时间
- repeatCount:重复次数,无限循环可以设置HUGE_VALF或者MAXFLOAT
- repeatDuration:重复时间
- fillMode:决定当前对象在非active时间段的行为。比如动画开始之前或者动画结束之后. 因为动画是在presenting tree上展现的,动画结束后是展现的模型树,presenting tree将被移除。
- beginTime:可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,CACurrentMediaTime()为图层的当前时间
CAAnimation的属性
- removedOnCompletion:默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillMode为kCAFillModeForwards
- timingFunction:速度控制函数,控制动画运行的节奏如:kCAMediaTimingFunctionLinear等
- delegate:动画代理(
CAAnimationDelegate
)代理方法监听动画的开始结束等状态
CABasicAnimation
随着动画的进行,在长度为duration
的持续时间内,keyPath
相应属性的值从fromValue
渐渐地变为toValue
.keyPath
内容是CALayer
的可动画Animatable属性如果fillMode=kCAFillModeForwards
同removedOnComletion=false
,那么在动画执行完毕后,图层会保持显示动画执行后的状态。但在实质上,图层的属性值还是动画执行前的初始值,并没有真正被改变。(只是没有移除掉presenting tree)
**注意:核心动画一切都是假象,并不会真实的改变图层的属性值,如果以后做动画的时候,不需要与用户交互,通常用核心动画(转场)。UIView动画必须通过修改属性的真实值,才有动画效果。
**
CAKeyframeAnimation
CAKeyframeAnimation
会使用一个NSArray保存动画的属性数值
- values:上述的NSArray对象。里面的元素称为“关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧
- path:可以设置一个CGPathRef、CGMutablePathRef,让图层按照路径轨迹移动。path只对
CALayer
的anchorPoint
和position
起作用。如果设置了path,那么values将被忽略 - keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧。如果没有设置keyTimes,各个关键帧的时间是平分的
CAAnimationGroup
CAAnimationGroup
可以保存一组动画对象,将CAAnimationGroup
对象加入层后,组中所有动画对象可以同时并发运行,主要的属性有
- animations:用来保存一组动画对象的Array默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间
CATransition
CATransition
用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。UINavigationController
就是通过CATransition实现了将控制器的视图推入屏幕的动画效果.主要的动画属性有:
- type:动画过渡类型
- subtype:动画过渡方向
- startProgress:动画起点(在整体动画的百分比)
- endProgress:动画终点(在整体动画的百分比)
type
属性控制着转场动画过渡效果
可以通过
+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion;
实现单视图的转场,而两个视图则得通过下面的方法
+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^)(BOOL finished))completion
定时器
当你需要定时的执行同一个效果的动画的时候,就需要使用定时器,iOS中有两种定时器,NSTimer
和CADisplayLink
。NSTimer
就是普通意义上的定时器,下面主要是讲CADisplayLink
CADisplayLink
CADisplayLink
是一种以屏幕刷新频率触发的时钟机制,每秒钟执行大约60次左右,可以使绘图代码与视图的刷新频率保持同步,而NSTimer
无法确保计时器实际被触发的准确时间,一个CADisplayLink
实例对象,会根据更新率定时同步绘画内容。需要提供一个选择器,当更新内容的时候。这个需要添加到一个RunLoop
当中的去(相当是添加了定时器类型的Runloop
)。
具体的使用使用方法:
- 定义CADisplayLink并制定触发调用方法
- 将显示链接添加到主运行循环队列
CADisplayLink
相关的属性有
-
duration
:每帧之间的时间 -
frameInterval
:就是间隔多少帧调用一次选择器。,默认值是1. -
paused
: 是否暂停当前的定时器,控制CADisplayLink
的运行 -
timestamp
:上一帧结束时候的时间,下一帧需要知道什么内容将被呈现
由于 CADisplayLink 绑定的方法会在每次屏幕刷新时被调用,精确度相当之高。正是基于这个特点,CADisplayLink 非常适合 UI 的重绘。