使用 CATransaction 同步 UIKIt 动画和 CoreAnimation 动画
UIKIt 动画,背后是 CoreAnimation 动画
同时使用 UIKIt 动画和 CoreAnimation 动画,是个好想法
下面的例子,使用 UIKIt 动画改 frame 尺寸大小,使用 CoreAnimation 来一个圆角动画
class ViewController: UIViewController {
var styledButton = { () -> UIButton in
let edge: CGFloat = 125
let btn = UIButton(frame: CGRect(x: 0, y: 0, width: edge, height: edge))
btn.layer.cornerRadius = edge/2
btn.backgroundColor = UIColor(red: 57/255.0, green: 73/255.0, blue: 171/255.0, alpha: 1)
return btn
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
styledButton.center = view.center
view.addSubview(styledButton)
}
@IBAction func start(_ sender: UIButton) {
// Call animation
animateButton()
}
fileprivate func animateButton(duration: CFTimeInterval = 1.0) {
let oldValue = styledButton.frame.width/2
let newButtonWidth: CGFloat = styledButton.frame.width/5
// 通过 CAMediaTimingFunction,设置一个弹簧动画
let timingFunction = CAMediaTimingFunction(controlPoints: 0.65, -0.55, 0.27, 1.55)
/* Do Animations */
CATransaction.begin()
CATransaction.setAnimationDuration(duration)
CATransaction.setAnimationTimingFunction(timingFunction)
// View animations
UIView.animate(withDuration: duration) {
self.styledButton.frame = CGRect(x: 0, y: 0, width: newButtonWidth, height: newButtonWidth)
self.styledButton.center = self.view.center
}
// Layer animations
let cornerAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.cornerRadius))
cornerAnimation.fromValue = oldValue
cornerAnimation.toValue = newButtonWidth/2
styledButton.layer.cornerRadius = newButtonWidth/2
styledButton.layer.add(cornerAnimation, forKey: #keyPath(CALayer.cornerRadius))
CATransaction.commit()
}
}
原理念经部分:
UIView 有一个 layer 属性 CALayer,CALayer 负责动画。
Core Animation 有三颗树,Layer Tree 层次树 ,Model Tree 模型树,Render Tree 渲染树
- 层次树,一般
addSubView、removeFromSuperView - 模型树,通过
layer.model()访问。
视图属性的改变,直接改模型树,直接跳过去了,没动画。
- 渲染树,通过
layer.presentation()访问。
动画过程中,视图属性的改变,改渲染树,有动画。
动画的一般操作,让模型树与渲染树,保持同步
视图的实际状态,就是动画开始状态
举个例子:
动画开始前,view.frame 等,就是视图的实际状态
CoreAnimation 的 fromValue ,就是动画的开始状态
Core Animation 开发遇到的两个状态
- 视图的给定状态 (frame 、alpha、color...)( 代码指定的 )
- 视图的运行状态 / 实际状态
就像代码控制一个视图从 a 点移动 到 b 点,
a 点和 b 点, 给定状态
视图运行在 a 点和 b 点 中间,实际状态
动画出了问题,就刷新状态
CATransaction.flush()
CATransaction 有一个协调的作用
CATransaction 可以把多个动画关联的行为组装好。 确保属性修改预期的动画,都同时提交到了 Core Animation.
同时使用 UIKit 动画和 CoreAnimation 动画,
CATransaction 作用域的动画,都会使用 CATransaction 的配置
举个例子:
因为 CATransaction 配置了动画时长, CATransaction.setAnimationDuration(duration),
CoreAnimation 就不需要指定 duration 了
没有 CATransaction 统一整合的效果:
圆角动画,和 frame 尺寸缩小动画,不同步
fileprivate func animateButton(duration: CFTimeInterval = 1.0) {
let oldValue = styledButton.frame.width/2
let newButtonWidth: CGFloat = styledButton.frame.width/5
// View animations
UIView.animate(withDuration: duration) {
self.styledButton.frame = CGRect(x: 0, y: 0, width: newButtonWidth, height: newButtonWidth)
self.styledButton.center = self.view.center
}
// Layer animations
let cornerAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.cornerRadius))
cornerAnimation.duration = duration
cornerAnimation.fromValue = oldValue
cornerAnimation.toValue = newButtonWidth/2
styledButton.layer.cornerRadius = newButtonWidth/2
styledButton.layer.add(cornerAnimation, forKey: #keyPath(CALayer.cornerRadius))
}
UIKit 框架非常友好, 直接改, 框架自动处理好了 View 属性动画和 Layer 属性动画的同步
fileprivate func animateButton(duration: CFTimeInterval = 1.0) {
let newButtonWidth: CGFloat = styledButton.frame.width/5
// View animations
UIView.animate(withDuration: duration) {
self.styledButton.frame = CGRect(x: 0, y: 0, width: newButtonWidth, height: newButtonWidth)
self.styledButton.center = self.view.center
self.styledButton.layer.cornerRadius = newButtonWidth/2
}
}