包含 CABasicAnimation 的一些简单动画,Spring 后面的再学。
最近开始忙项目了,停较长一段时间
6. Layer
Layer 概述
Layer 是一个简单的对象,只对外提供一些图像显示方面的内容相关的属性,每一个 UIView 背后都有一个 Layer 层。
Layer 跟 UIView 是不一样的,主要因为:
- Layer 只是一个简单的模型对象,不像 UIView 那么逻辑对象,没有复杂的自动布局和手势识别等。
- Layer 预定义了很多可见的特性,这些特性是一系列的呈现在屏幕上的内容的数据,比如边界线、边框颜色、位置、阴影等。
- Core Animation 优化了对 layer 的支持,比如缓存,并且直接使用 GPU 进行绘制。
UIView 之所以复杂,是因为他还负责了 NSAutoLayout 用户交互等复杂的逻辑,但是Layer没有任何逻辑,只是提供了缓存和 GPU 绘制。
所以 UIView 非常灵活机动,有很多的子类,但是 Layer 子类就非常少,层级更加简单。
UIView 任何时候都能胜任工作,但是如果要考虑优化和性能的时候就得考虑 Layer 了。
Core Animation 操作 Layer 同样跟 UIView 一样通过设定起终点的状态以及过度方式来操作,只是在 Layer 中不仅仅可以操作之前的:frame、position、opacity,还有更多属性可以修改。
6.1 Layer 属性
定位与尺寸 position size
- bounds : 改变layer的边界frame
- positon : 可以理解成 UIView 的 center,具有 x y 两个属性。
- transform : Layer 的 transform 还可以 3D 变换,修改 scala, 角度 rotate .
边界 border
- borderColor : tint 边界颜色
- borderWidth : 边界宽度
- borderRadius : 圆角矩形圆角半径
阴影 shadow
- shadowOffset: 代表阴影跟Layer的偏移
- shadowOpacity: 修改这个值让阴影渐进渐出
- shadowPath: 修改 Layer 的阴影的形状
- shadowRadius: 让阴影变模糊
内容 Contents
最后这些属性控制着最后给 Layer 展示的数据,直接用户看到的一层
- contents: 修改这个值去分配原生的 TIFF 或 PNG 数据给 Layer 的内容
- mask: 修改这个值去重建形状跟图像。
- opacity: 透明度
UIView 的动画不能指定开始状态,但是 Layer 很多情况下可以指定开始状态。
6.2 CABasicAnimation
创建一个 CABasicAnimation 对象:
然后创建一个 CABasicAnimation 对象,然后添加到 view 的 layer 上,keyPath
用来指定动画的属性,上面提到过的属性都可以作为动画目标属性。
let flyRight = CABasicAnimation(keyPath: "position.x")
flyRight.fromValue = -view.bounds.size.width/2
flyRight.toValue = view.bounds.size.width/2
flyRight.duration = 0.5
//把动画添加到 layer 上
heading.layer.add(flyRight, forKey: nil)
当我们这样创建一个 CABasicAnimation 的时候,flyRight 只是一个对象,它跟任何一个 layer 都没有关系。当 add 方法调用的时候,传入绑定 Layer 的是一个它的 copy,并不是我们创建的这个对象本身。
所以我们可以复用 CABasicAnimation 对象。
fillMode 动画模式
使用动画对象的这个属性,让你控制动画在开始到结束的整个流程出现的状态。
关于 Layer 动画状态的控制,通过 fillMode
变量来控制 layer 起始状态的展示。
用动画开始和结束来分割一个 Layer 的时间线,可以分为三个区域,动画开始前,动画中,以及动画结束后,fillMode 的四个值,包括了动画的四种前后帧的留存情况。
通过设置 动画的 fillMode 属性,可以控制动画帧在动画开始之前以及结束之后的停留与否的影响,有四个枚举值:
- kCAFillModeRemoved : 仅动画期间
- kCAFillModeBackwards : 以及开始之前
- kCAFillModeForwards : 以及结束之后
- kCAFillModeBoth : 包括开始结束
所以当你改变了动画的开始结束时间,应该考虑到 presentation layer 层的留存情况。
比如延迟动画时间:
flyRight.beginTime = CACurrentMediaTime() + 0.3
flyRight.fillMode = kCAFillModeBoth
另一个属性是 isRemovedOnCompletion
,这个属性默认值为 true,结束的时候如果要保留 Layer 位置,就要设置为 false。
动画层 与 Layer 层
当执行动画的时候,我们看到的不是真正的 Layer,而是一个 presentation layer,动画执行完毕,展示的 Layer 就会移出视图,然后重新展示原始的 Layer。
展示 Layer 也不接收交互事件的。
CABasicAnimation 有一个属性 isRemoveOnCompletion
,这个属性结合上一个 fillMode
,设置前者为 false,后者为kCAFillModeBoth
,可以在动画结束的时候,把动画最后的一个 Layer 留存下来。
但是尽量避免保留动画的 Layer 层,考虑性能以及手势的问题,当一个动画运行完毕,就移出界面,然后更新 UIView 的属性。
6.2 动画 Keys & Delegate
CAAnimationDelegate
UIKit API 的 UIView 动画只能根据 Closure 进行动画,一旦开始,不能暂停停止或者获取动画对象。
相比之下 CA 动画的机动性就比较强,有获取动画对象、设置代理方法。
CAAnimation 跟 CABasicAnimation 都遵守了代理 CAAnimationDelegate
,开始结束的代理方法如下:
extension ViewController: CAAnimationDelegate {
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
print("animation did finish.")
}
func animationDidStart(_ anim: CAAnimation) {
print("Start!")
}
}
KVC
CAAnimation 基于 OC 实现,所以 KVC KVO 都是可用的。
可以使用 KVC KVO 给 CAAnimation 对象设置 value,存取案例:
flyRight.setValue("form", forKey: "name")
flyRight.setValue(heading.layer, forKey: "layer")
//读取
guard let name = anim.value(forKey: "name") as? String else {
return
}
动画 Keys
在给 layer 添加 Animation 的时候,有一个 key
参数,这个参数是用来指定动画的标示,让我们方便获取动画对象,以便甚至在动画运行的时候去控制它。
当多个动画各自独立执行的时候,这些 Key
参数就可以发挥作用了。
执行中的动画
Layer 有一个方法 layer.animationKeys()
,获取所有动画的数组
获取动画可以做:
- 执行
removeAllAnimations()
取消所有动画,或者removeAnimation(forKey:)
根据 key 取消动画 - 通过
animationKeys()
遍历动画,获取的都是 immutable 对象,在他们执行的时候并不能进行修改。 - 获取某一个动画通过
animationForKey(_:)
,获取的是 immutable 对象
6.3 动画组
多个动画打包成一个动画,多动画的同步,实现动画属性的统一管理。
当多个动画运行在一个 Layer 上面的时候,很多时候我们需要让这些动画相互同步,使用 CAAnimationGroup
可以解决这个问题。表面上只是将多个动画合并,统一处理,背后的动画同步等机制,全部由 CAAnimationGroup 来完成。
实现如下:
// 1️⃣:像正常创建 CABasicAnimation 一样创建 CAAnimationGroup,他们都是继承自 `CAAnimation`,所以属性也有 `beginTime` `duration` `fillMode` `delegate` `isRemoveOnCompletion`。
let groupAnimation = CAAnimationGroup()
groupAnimation.beginTime = CACurrentMediaTime()
groupAnimation.duration = 0.5
groupAnimation.fillMode = kCAFillModeBackwards
// 2️⃣:然后单独创建三个 CABAsicsAnimation
let scaleDown = CABasicAnimation(keyPath: "transform.scale")
scaleDown.fromValue = 3.5
scaleDown.toValue = 1.0
let rotate = CABasicAnimation(keyPath: "transform.rotation")
rotate.fromValue = .pi/4.0
rotate.toValue = 0.0
let fade = CABasicAnimation(keyPath: "opacity")
fade.fromValue = 0.0
fade.toValue = 1.0
// 3️⃣:最后通过对 group 动画设置动画数组的方法,分开的动画打成动画组,通过设置 groupAnimation 对象的 animations 属性,然后调用 layer 的 add 方法
groupAnimation.animations = [scaleDown, rotate, fade]
loginButton.layer.add(groupAnimation, forKey: nil)
但是说什么动画可以打包,什么动画需要独立编写,需要自己通过经验去衡量把握。比如一个启动界面的 ViewDidAppear 里面的动画,在刚进入界面的时候开始,还是应该打成组的。
慢入慢出 EaseInOut
EaseInOut 在 UIView 的动画中通过设置调用 UIView 的 animate
方法中的 options
参数配置。
在 Layer 中也比较类似,只是语法上有一些区别,通过设置 Animation 对象的 timingFunction
属性来实现,这个属性是一 CAMediaTimingFunction
对象,继承自 NSObject, 通过初始化方法指定 name
参数为一下四个预定义的值,即可创建预定义好的慢入慢出对象。
CAMediaTimingFunction 初始化方法的四个预定义 name
的值:
- kCAMediaTimingFunctionLinear
- kCAMediaTimingFunctionEaseIn
- kCAMediaTimingFunctionEaseOut
- kCAMediaTimingFunctionEaseInEaseOut
这四个函数跟前面 UIView 的慢入慢出类似,第一个是正常,第二个是慢入,第三个慢出,第四个慢入慢出。
groupAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
我们可以创建自定义的 CAMediaTimingFunction
对象,通过 init 方法 controlPoints
,传入四个贝赛尔曲线的两个控制点的坐标,即可以实现,不赘述。
动画重复 RepeatCount
设置动画的重复次数,即结束之后,重复从头开始动画,通过设置 CABasicAnimation 的属性 repeatCount
,设置重复的次数。
另外的一个属性 autoreverses
,设置自动逆向播放,动画完成之后,再逆向动画一次。
通过同时设置这两个参数,可以时间循环逆向播放动画。一轮正逆算 repeat 一次。
如果设置动画对象的 repeatCount
为 1.5,则动画的第二次动画的逆向动画取消,第二次只播放一次正向动画。
动画速度 speed
CABasicAnimation 的参数 speed
,可以控制动画的速度,默认值为 1.0f。
其实:
动画的实际消耗时间 * speed = duration
单独设置 CABasicAnimation 对象的 speed
之外,还可以直接设置 Layer 的 speed
,比如设置 self.view.layer.speed = 2.0
,viewController 的 view 之下的所有动画都会以 2.0 的速度播放。