创建动画类(CABasicAnimation, CAKeyframeAnimation,CASpringAnimation)如下:
基础动画
CATransaction.begin()
self.layerView.backgroundColor = UIColor.green.cgColor
let baseanimation = CABasicAnimation()
baseanimation.keyPath = "backgroundColor" // 这个值为 keypath 的值
baseanimation.fromValue = UIColor.green.cgColor
baseanimation.toValue = UIColor.yellow.cgColor
baseanimation.isRemovedOnCompletion = false
baseanimation.fillMode = .forwards // 这个属性生效需要 isRemovedOnCompletion == false
// CATransaction.setDisableActions(true) // 关闭隐式动画
CATransaction.setAnimationDuration(3) // 默认 0.25s
let during = CATransaction.animationDuration()
print("during:\(during)")
layerView.add(baseanimation, forKey: "abc")
CATransaction.commit()
- 为了实现动画结束后,颜色保持不变的另一种实现方式1
CATransaction.begin()
self.layerView.backgroundColor = UIColor.green.cgColor
let baseanimation = CABasicAnimation()
baseanimation.keyPath = "backgroundColor"
baseanimation.fromValue = UIColor.green.cgColor
CATransaction.setAnimationDuration(2)
let during = CATransaction.animationDuration()
print("during:\(during)")
layerView.backgroundColor = UIColor.yellow.cgColor
layerView.add(baseanimation, forKey: "animationBackground")
CATransaction.commit()
*为了实现动画结束后,颜色保持不变的另一种实现方式2
@objc func changeToGreen() {
CATransaction.begin()
self.layerView.backgroundColor = UIColor.green.cgColor
let baseanimation = CABasicAnimation()
baseanimation.keyPath = "backgroundColor"
baseanimation.fromValue = UIColor.green.cgColor
baseanimation.toValue = UIColor.yellow.cgColor
CATransaction.setDisableActions(true)
CATransaction.setAnimationDuration(2)
let during = CATransaction.animationDuration()
print("during:\(during)")
baseanimation.delegate = self
layerView.add(baseanimation, forKey: "animationBackground")
CATransaction.commit()
}
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
CATransaction.begin()
// 如果不关闭这回出现两此从 green 变成 yellow 的动画
// 第二次为隐式动画
CATransaction.setDisableActions(true)
layerView.backgroundColor = UIColor.yellow.cgColor
CATransaction.commit()
}
关键帧动画
基础使用方法-颜色变化
@objc func changeToGreen() {
CATransaction.begin()
self.layerView.backgroundColor = UIColor.green.cgColor
let baseanimation = CAKeyframeAnimation()
baseanimation.keyPath = "backgroundColor"
baseanimation.values = [UIColor.green.cgColor, UIColor.black.cgColor, UIColor.yellow.cgColor]
CATransaction.setDisableActions(true)
CATransaction.setAnimationDuration(2)
let during = CATransaction.animationDuration()
print("during:\(during)")
baseanimation.delegate = self
layerView.add(baseanimation, forKey: "animationBackground")
CATransaction.commit()
}
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
CATransaction.begin()
CATransaction.setDisableActions(true)
layerView.backgroundColor = UIColor.yellow.cgColor
CATransaction.commit()
}
飞机飞行动画
private let layerView = CAShapeLayer()
@objc func changeToGreen() {
// 画出飞机的线路
CATransaction.begin()
CATransaction.setDisableActions(true)
layerView.frame = view.frame
layerView.path = getBezierPath().cgPath
layerView.fillColor = UIColor.red.cgColor
layerView.strokeColor = UIColor.blue.cgColor
layerView.lineWidth = 3.0
CATransaction.commit()
// 飞机飞行的动画
CATransaction.begin()
let shipLayer = CALayer()
shipLayer.frame = CGRect(x: 0, y: 0, width: 64, height: 64)
shipLayer.position = CGPoint(x: 0, y: 150)
shipLayer.contents = UIImage(named: "plane.png")?.cgImage
view.layer.addSublayer(shipLayer)
let animation = CAKeyframeAnimation()
animation.keyPath = "position"
animation.duration = 4.0
animation.rotationMode = .rotateAuto // 根据飞行线路的切线改变飞机头的方向,苹果自带效果
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
animation.path = getBezierPath().cgPath
shipLayer.add(animation, forKey: "move")
CATransaction.commit()
}
虚拟属性
没有显示的提供属性;如:考虑一个旋转的动画:如果想要对一个物体做旋转的动画,那就需要作用于transform属性,因为CALayer没有显式提供角度或者方向之类的属性,
// 飞机飞行的动画
CATransaction.begin()
let shipLayer = CALayer()
shipLayer.frame = CGRect(x: 150, y: 0, width: 64, height: 64)
shipLayer.position = CGPoint(x: 200, y: 150)
shipLayer.contents = UIImage(named: "plane.png")?.cgImage
view.layer.addSublayer(shipLayer)
let animation = CABasicAnimation()
animation.keyPath = "transform" // 虚拟属性
animation.duration = 4.0
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
animation.toValue = CATransform3DMakeRotation(CGFloat.pi, 0, 0, 1)
shipLayer.add(animation, forKey: "move")
CATransaction.commit()
另外一种实现方式:
CATransaction.begin()
let shipLayer = CALayer()
shipLayer.frame = CGRect(x: 150, y: 0, width: 64, height: 64)
shipLayer.position = CGPoint(x: 200, y: 150)
shipLayer.contents = UIImage(named: "plane.png")?.cgImage
view.layer.addSublayer(shipLayer)
let animation = CABasicAnimation()
animation.keyPath = "transform.rotation" // 虚拟属性
animation.duration = 4.0
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
animation.byValue = CGFloat.pi * 2
shipLayer.add(animation, forKey: "move")
CATransaction.commit()
用transform.rotation而不是transform做动画的好处如下:
- 我们可以不通过关键帧一步旋转多于180度的动画。
可以用相对值而不是绝对值旋转(设置byValue而不是toValue)。
可以不用创建CATransform3D,而是使用一个简单的数值来指定角度。
不会和transform.position或者transform.scale冲突(同样是使用关键路径来做独立的动画属性)。 - transform.rotation属性有一个奇怪的问题是它其实并不存在。这是因为CATransform3D并不是一个对象,它实际上是一个结构体,也没有符合KVC相关属性,transform.rotation实际上是一个CALayer用于处理动画变换的虚拟属性。
- 你不可以直接设置transform.rotation或者transform.scale,他们不能被直接使用。当你对他们做动画时,Core Animation自动地根据通过CAValueFunction来计算的值来更新transform属性。
- CAValueFunction用于把我们赋给虚拟的transform.rotation简单浮点值转换成真正的用于摆放图层的CATransform3D矩阵值。你可以通过设置CAPropertyAnimation的valueFunction属性来改变,于是你设置的函数将会覆盖默认的函数。
- CAValueFunction看起来似乎是对那些不能简单相加的属性(例如变换矩阵)做动画的非常有用的机制,但由于CAValueFunction的实现细节是私有的,所以目前不能通过继承它来自定义。你可以通过使用苹果目前已近提供的常量(目前都是和变换矩阵的虚拟属性相关,所以没太多使用场景了,因为这些属性都有了默认的实现方式)。
动画组
CABasicAnimation和CAKeyframeAnimation仅仅作用于单独的属性,而CAAnimationGroup可以把这些动画组合在一起。CAAnimationGroup是另一个继承于CAAnimation的子类,它添加了一个animations数组的属性,用来组合别的动画。如下:
private let layerView = CAShapeLayer()
@objc func changeToGreen() {
// 画出飞机的线路
CATransaction.begin()
CATransaction.setDisableActions(true)
layerView.frame = view.frame
layerView.path = getBezierPath().cgPath
layerView.fillColor = UIColor.red.cgColor
layerView.strokeColor = UIColor.blue.cgColor
layerView.lineWidth = 3.0
CATransaction.commit()
// 飞机飞行的动画
CATransaction.begin()
let shipLayer = CALayer()
shipLayer.frame = CGRect(x: 150, y: 0, width: 64, height: 64)
shipLayer.position = CGPoint(x: 200, y: 150)
shipLayer.contents = UIImage(named: "plane.png")?.cgImage
view.layer.addSublayer(shipLayer)
let animation = CABasicAnimation()
animation.keyPath = "transform.rotation" // 虚拟属性
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
animation.byValue = CGFloat.pi * 2
let animation1 = CAKeyframeAnimation()
animation1.keyPath = "position"
animation1.rotationMode = .rotateAuto // 根据飞行线路的切线改变飞机头的方向,苹果自带效果
animation1.fillMode = .forwards
animation1.isRemovedOnCompletion = false
animation1.path = getBezierPath().cgPath
let group = CAAnimationGroup()
group.animations = [animation, animation1]
group.duration = 4.0
shipLayer.add(group, forKey: "move")
CATransaction.commit()
}
func getBezierPath() -> UIBezierPath {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addQuadCurve(to: CGPoint(x: 200, y: 50),
controlPoint: CGPoint(x: 100, y: 100))
path.move(to: CGPoint(x: 200, y: 50))
path.addQuadCurve(to: CGPoint(x: 400, y: 50),
controlPoint: CGPoint(x: 300, y: 150))
return path
}
过渡
- 给层添加默认动画, 可以做图片替换的过渡动画
private let shipLayer = CALayer()
private var index = 0
@objc func changeToGreen() {
defer {
index += 1
}
if index % 2 == 0 {
shipLayer.contents = UIImage(named: "plane.png")?.cgImage
} else {
shipLayer.contents = UIImage(named: "ship.png")?.cgImage
}
view.layer.addSublayer(shipLayer)
let transation = CATransition()
transation.type = .reveal
shipLayer.frame = CGRect(x: 150, y: 0, width: 64, height: 64)
shipLayer.add(transation, forKey: "moveIn")
}
以上为对图层树的动画
对于 tabbarcontroller 选择的时候做添加 CATransition 的动画会有意向不到的事情
CATransition并不作用于指定的图层属性,这就是说你可以在即使不能准确得知改变了什么的情况下对图层做动画,例如,在不知道UITableView哪一行被添加或者删除的情况下,直接就可以平滑地刷新它,或者在不知道UIViewController内部的视图层级的情况下对两个不同的实例做过渡动画。
自定义动画
- 系统利用 CIFliter 等,做的一些动画。当然效果也可以自己根据上下文创建一个图片,然后根据需求做出自定义的一些动画
let imageView = UIImageView()
private var index = 0
@objc func changeToGreen() {
defer {
index += 1
}
let image: UIImage?
if index % 2 == 0 {
image = UIImage(named: "plane.png")
} else {
image = UIImage(named: "ship.png")
}
UIView.transition(with: self.imageView, duration: 1.2, options: UIView.AnimationOptions.transitionCurlUp) {
self.imageView.image = image
} completion: { _ in
}
}