Core Animation 学习笔记1 - UIView & AL 动画
我自己的 Core Animation 的学习笔记,来自 Ray 家 iOS 最好的动画书籍 -- iOS Animations by Tutorials
支持正版
1.UIKit 动画 API
UIView 的最基本的动画方法如下:
open class func animate(withDuration duration: TimeInterval, animations: @escaping () -> Swift.Void)
带有延迟的方法
open class func animate(withDuration duration: TimeInterval, delay: TimeInterval, options: UIViewAnimationOptions = [], animations: @escaping () -> Swift.Void, completion: (@escaping (Bool) -> Swift.Void)? = nil)
options 用来配置动画的一些参数,具体后面有详细。
可以传入数组,数据的元素是 UIViewAnimationOptions 枚举的元素,代表选择多个动画的配置参数,传入[] 代表为空。
1.1 动画属性
UIView 的动画 API 中,不是所有的属性在修改的时候会形成动画效果的。
常见的可以产生动画的属性
bounds, frame, center
基本的定位和 view 的大小 Size 是可以被修改的,这其中包括了 view 的 bounds, frame 和 center,通过修改这三个属性来实现 放大
缩小
和 移动
。
backgroundColor 和 alpha
· 外观相关的属性有 UIView 的 背景颜色 backgroundColor 和 透明度 alpha 值。
Transformation 视图变形 / 仿射变换
· 在动画的 block 中改变 UIView 的 偏转角度(rotation),大小缩放(scala),定位(position)
Transformation 跟 第一种的区别在于:
Transformation 的底层实现是由 Affine Transform 仿射变换的原理完成,具体原理是通过一系列的矩阵数学计算来完成,(具体原理稍后研究补上);而 UIView 属性就是通过改变 UIView 的frame center等属性完成的动画。
从某些变化的角度来说,Transform 更强大,比如它可以控制旋转角度,可以仅仅提供一个 scala 参数而无需提供具体的frame,某些参数更加贴近现实语言。
同样是让视图变化,Transformation 是由 Affine Transform 仿射变换的原理完成,不会强硬的去改变
1.2 基本的可选配置
动画方法的 options 中通过一个数组来配置动画的一些属性。
options 传参的四种写法:
1. [] 代表无参数
2. .Repeat 代表一个 .Repeat 参数
3. [.Repeat] 代表一个 .Repeat 参数
4. [.Repeat, . Autoreverse] 代表两个参数
下面列举了可选的参数:
重复
.Repeat
:会让你的动画重复循环
与之对应的有一个参数是
.Autoreverse
:配合着 .Repeat 一起用,代表自动反转,如下例子:
UIView.animateWithDuration(0.5, delay: 0.4, options: [.Repeat, .Autoreverse], animations: {
self.password.center.x += self.view.bounds.width
}, completion: nil)
慢入慢出
.EaseIn
, .EastOut
的设置让你的元素动画起来更符合现代物理学的特征,引入了加速度和减速度的概念,显得更加真实,不会突然启动,也不会突然停止。
option 中通过四个可选参数来控制:
.Linear
:代表匀速,没有加速和减速的存在。
.CurveEasyIn
: 在动画开始的时候加速度启动
.CurveEasyOut
: 在动画开始的时候减速度结束
.CurveEasyInOut
: 结合上面两中,开始和结束的时候有加速度和减速度。慢入慢出的效果。
2. 弹性动画
open class func animate(withDuration duration: TimeInterval, delay: TimeInterval, usingSpringWithDamping dampingRatio: CGFloat, initialSpringVelocity velocity: CGFloat, options: UIViewAnimationOptions = [], animations: @escaping () -> Swift.Void, completion: (@escaping (Bool) -> Swift.Void)? = nil)
关键词是 usingSpringWithDamping,这里的两个属性:
Damping
,这个代表着阻尼,取值范围从 0.0~1.0;
Velocity
,代表元素的起始速度,初速度越大,弹的幅度也就越大
3. Transitions 过渡动画
过渡动画是提前预定义好的一些列动画,你可以直接使用,使得界面过渡自然。有很多 3D 效果,大部分我们在 iPhone 历代中都见过,可以写 Demo 都试一遍,非常方便使用。
各种使用场景:
3.1 添加新 View:
public class func transitionWithView(view: UIView, duration: NSTimeInterval, options: UIViewAnimationOptions, animations: (() -> Void)?, completion: ((Bool) -> Void)?)
在动画 closure 内简单的 addSubview 即可
这个方法跟之前的 UIView 动画方法类似,但是多了一个 view 的参数,这个参数穿进去的 view 可以被 options 中的预定义动画参数定义过渡动画。
.transitionFlipFromLeft
.transitionFlipFromRight
.transitionCurlUp
.transitionCurlDown
.transitionCrossDissolve
.transitionFlipFromTop
.transitionFlipFromBottom
view: 的参数写要添加的View的superView。
3.2 移除 View:
跟上面的方法一样,在动画 closure 内简单的调用 removeFromSuperview 即可完成,view 参数传要移除的 view 的 superView。
3.3 隐藏显示:
跟上面的方式一样,view参数传需要改变 alpha 值的 view 本身。
3.4 替换图片
替换的过渡效果的方法是:
public class func transition(with view: UIView, duration: TimeInterval, options: UIViewAnimationOptions = [], animations: (() -> Swift.Void)?, completion: ((Bool) -> Swift.Void)? = nil)
from 与 to 的都是 View。
4. 链式动画 关键帧动画
keyFrames 动画核心是下面两个 API,UIView 调用一个animateKeyframes
方法,然后在这个方法的 closure 里面去 调用 UIView.addKeyframe
方法来添加关键帧动画。
animateKeyframes 动画的 options 是 'UIViewKeyFrameAnimationOptions',跟前面的不一样,之前的动画是 UIViewAnimationOptions
。
extension UIView {
@available(iOS 7.0, *)
public class func animateKeyframes(withDuration duration: TimeInterval, delay: TimeInterval, options: UIViewKeyframeAnimationOptions = [], animations: () -> Swift.Void, completion: ((Bool) -> Swift.Void)? = nil)
@available(iOS 7.0, *)
public class func addKeyframe(withRelativeStartTime frameStartTime: Double, relativeDuration frameDuration: Double, animations: () -> Swift.Void)
// start time and duration are values between 0.0 and 1.0 specifying time and duration relative to the overall time of the keyframe animation
}
UIView.addKeyframe
添加关键帧动画的方法有好多个,上面这个方法涉及两个参数:relativeStartTime
和 relativeDuration
:
relativeStartTime : 指这个动画在整个关键帧动画中的开始时间,是一个相对时间,比如这个参数设置为 0.2,就是在整个动画的 duration * 0.2 开始,理解为 20% 的时候开始这个子动画
relativeDuration : 动画的持续时间,也是一个相对值,0.25就是执行时间占整个动画的 25%
最后,关键帧动画本身不支持内建的慢入慢出动画,关键帧动画本身是设计来解决固定开始结束时间的动画 和 在动画序列之间顺序切换的动画,所以在 EasyInOut 上不做内建支持,也就是说它没办法像 UIView 标准动画一样通过设置一个 EasyInOut 的 options 参数就实现令人满意的慢入慢出效果。
替代方案是有一系列计算模型,官方文档中 UIViewKeyframeAnimationOptions
有详细描述。
CG放射变幻的角度:
self.planeImage.transform = CGAffineTransform(rotationAngle: (CGFloat(-M_PI_4/2)))
5. Auto Layout Animation
获取已有的constant
可以通过 IB 工具把 constant 设置为一个 outlet,也可以通过 reference 在代码创建的时候设置一个 weak 引用。
运行时可以通过循环遍历找到 constant,遍历用 swift 的数组属性遍历方法,来找到正确的那一个 constraint:
view.superview?.constraints.forEach { constraint in
//your code...
if constraint.firstItem == titleLabel &&
constraint.firstAttribute == .centerX {
constraint.constant = isMenuOpen ? -100.0 : 0.0
break
}
}
更快遍历找到一个 constraint,可以在 IB 中设置这个 constraint 的 Identifier。然后 for-in 遍历的时候直接判断 ID :
if constraint.identifier == "TitleCenterY"
修改 constant 产生动画
在动画闭包之前手动更新 Layout,然后在动画 Closure 中调用 layoutIfNeeded
一般使用 AL 来做静态的布局,使用了 AL 之后就无法重新设置 UI 元素的 center bounds 以及 frame 了,否则会根据 constraints 走,再做动画的时候就必须做 AL 相关的动画。
AL 动画跟 UIView 属性的动画都很相似,只不过 AL 动画一般是替换了之前的约束,然后让 UIView 元素的两个状态动画变换起来。
实现起来就是 Outter 需要变更的 Constraint,然后改变 约束的 constant。然后在改变 constant 的代码后面加上
UIView.animate(withDuration: 1.0, delay: 0.0, usingSpringWithDamping: 0.4, initialSpringVelocity: 10.0, options: .curveEaseIn, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
在 animation 的 closure 中,调用 layoutIfNeeded
,强制更新布局,产生动画。
替换 constraint 产生动画
通过设置一个 constant 的 isActive
属性为 false,可以暂时关闭这个 constant,然后通过代码正常添加一个 constant 或者激活一个新的,产生替换 constant 的效果。
创建一个新的 constant 并设置 isActive 为 true:
let newConstraint = NSLayoutConstraint(
item: titleLabel,
attribute: .centerY,
relatedBy: .equal,
toItem: titleLabel.superview!,
attribute: .centerY,
multiplier: isMenuOpen ? 0.67 : 1.0,
constant: 5.0)
newConstraint.identifier = "TitleCenterY"
newConstraint.isActive = true
新View动画
首先像正常一样创建一个 View,加到 subview 里,然后添加 constraints。
可以使用 NSLayoutAnchor
这个新类,让你创建 constraint 的过程变得简单,常用的一些都很好用,下面代码最后几行每一行都是创建一个 NSLayoutAnchor :
func showItem(_ index: Int) {
print("tapped item \(index)")
let imageView = UIImageView(image: UIImage(named: "summericons_100px_0\(index).png"))
imageView.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.5)
imageView.layer.cornerRadius = 5.0
imageView.layer.masksToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(imageView)
let conX = imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let conBottom = imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: imageView.frame.height)
let conWidth = imageView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.33, constant: -50.0)
let conHeight = imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor)
NSLayoutConstraint.activate([conX, conBottom, conWidth, conHeight])
}
⚠️如果是代码使用的 AL,需要设置 translatesAutoresizingMaskIntoConstraints 为 false。
创建好了 constraints 之后,确保这些 constraints 标明了动画的起始位置,然后调用一遍 layoutIfNeeded,进行静态的布局。
然后像上面一样,调用 UIView 的标准动画方法,弹性或者慢入慢出都可以,closure 中然后修改正确的 constant 值,调用 layoutIfNeeded 方法就生效了。