引言
本篇博客紧接着上一篇的隐式动画开始介绍显式动画。隐式动画是创建动态页面的一种简单的直接的方式,也是UIKit的动画机制基础。但是它并不能涵盖所有的动画类型。
显式动画
接下来我们就来研究另外一种动画显式动画,它能够对一些属性做指定的动画,或者非线性动画。
显式动画 - 属性动画
给一个图层的属性添加动画时,我们需要借助CAAnimationDelegate代理,这个代理我们可以在CAAnimation的文件中找到,它有两个方法:
动画已经开始
optional func animationDidStart(_ anim: CAAnimation)
动画已经结束
optional func animationDidStop(_ anim: CAAnimation, finished flag: Bool)
下面的例子中我们使用属性动画来修改图层的颜色,并在动画结束之后来设置图层最终颜色。
这里面有一个需要注意的地方,如果我们给单独的图层添加动画,需要在一个新的事物中来进行,并且禁用图层的默认行为,否则动画会发生两次,一次是我们设置的显式动画,一个是图层默认的隐式动画。
代码如下:
class ViewController: UIViewController, CAAnimationDelegate{let colorLayer = CALayer()override func viewDidLoad() {super.viewDidLoad()// Do any additional setup after loading the view.colorLayer.frame = CGRect(x: 50, y: 100, width: 100, height: 100)colorLayer.backgroundColor = UIColor.red.cgColorself.view.layer.addSublayer(colorLayer)}override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {let colors = [UIColor.red.cgColor, UIColor.green.cgColor, UIColor.blue.cgColor]let animation = CABasicAnimation()animation.keyPath = "backgroundColor"animation.toValue = colors.randomElement()animation.duration = 1.0animation.delegate = selfcolorLayer.add(animation, forKey: nil)}func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {if let animation = anim as? CABasicAnimation {CATransaction.begin()CATransaction.setDisableActions(true)let cgColor = animation.toValuecolorLayer.backgroundColor = (cgColor as! CGColor)CATransaction.commit()}}}
动画的结束事件,是使用代理的方法来实现的,这样会有一个问题。就是假如一个控制器中有多个图层进行动画,那么所有的动画都会使用这一个回调方法,这样的话我们就需要判断是哪个图层动画的回调。
在添加动画时我们发现open func add(_ anim: CAAnimation, forKey key: String?)有一个key属性,目前设置的为nil。事实上它就是东湖的唯一标识符,我们给它设置一个非空的唯一字符串,在回调的时候就可以对所有图层进行循环,调用-animationForKey:来比对结果。
我们来修改一下代码:
colorLayer.add(animation, forKey: "colorChange")
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {if colorLayer.animation(forKey: "colorChange") == anim {if let animation = anim as? CABasicAnimation {CATransaction.begin()CATransaction.setDisableActions(true)let cgColor = animation.toValuecolorLayer.backgroundColor = (cgColor as! CGColor)CATransaction.commit()}}}
但是当有很多很多图层有很多很多动画的话,这个方法就显得不太优雅了。
不过我们还有更简单的版本。CAAnimation也显示了KVC协议,所以我们可以使用open func setValue(_ value: Any?, forKey key: String)和open func value(forKey key: String) -> Any?两个方法来存取属性。但是CAAnimation还有一个不同的地方,它更新时一个NSDictionary,我们可以随意设置键值对。
这就意味着我们可以对动画打任何类型的标签,下面我们就来写个例子感受一下这个特性:
class ViewController: UIViewController, CAAnimationDelegate{let firstView = UIView()let secondView = UIView()let thirdView = UIView()override func viewDidLoad() {super.viewDidLoad()self.view.addSubview(firstView)firstView.backgroundColor = .redfirstView.frame = CGRect(x: 40.0, y: self.view.bounds.size.height - 300.0, width: 40.0, height: 10.0)self.view.addSubview(secondView)secondView.backgroundColor = .greensecondView.frame = CGRect(x: 80.0, y: self.view.bounds.size.height - 300.0, width: 40.0, height: 10.0)self.view.addSubview(thirdView)thirdView.backgroundColor = .bluethirdView.frame = CGRect(x: 120.0, y: self.view.bounds.size.height - 300.0, width: 40.0, height: 10.0)}override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {addAnimation(view: firstView, height: 100.0)addAnimation(view: secondView, height: 200.0)addAnimation(view: thirdView, height: 300.0)}func addAnimation(view:UIView,height:CGFloat) {let animation = CABasicAnimation()animation.keyPath = "bounds.size.height"animation.toValue = heightanimation.duration = 1.0animation.delegate = selfanimation.setValue(view, forKey: "view")view.layer.add(animation, forKey: nil)}func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {guard let anim = anim as? CABasicAnimation else {return}if let view = anim.value(forKey: "view") as? UIView {let height = anim.toValue as? CGFloatview.layer.bounds.size.height = height!}}}
点击屏幕后发现,每个动画的结果都对应到了自己的图层,效果如下:
可以做动画的属性和虚拟属性非常的多,我们就来列举一些常见的几何属性吧
- tranform.rotation.x 按x轴旋转的弧度
- tranform.rotation.y 按y轴旋转的弧度
- tranform.rotation.z 按z轴旋转的弧度
- tranform.rotation 按z轴旋转的弧度,和tranform.rotation.z效果一样
- tranform.scale.x 在x轴按比例放大缩小
- tranform.scale.y 在y轴按比例放大缩小
- tranform.scale.z 在z轴按比例放大缩小
- tranform.scale 整体按比例放大缩小
- transform.translation.x 沿x轴平移
- transform.translation.y 沿y轴平移
- transform.translation.z 沿z轴平移
- transform.translation x,y坐标均发生改变
- transform CATransform3D 4xbounds4矩阵
- bounds layer大小
- position layer位置
- anchorPoint 锚点位置
- cornerRadius 圆角大小
- ZPosition Z轴位置
显式动画 - 关键帧动画
CAKeyframeAnimation是另一种UIKit没有暴露出来的且功能十分强大的类。和CABasicAnimation一样它也是继承自CAPropertyAnimation,并且它也作用于单一的一个属性,不同的是它不是只设置一个起始值和一个结束值。而是可以设置一连串随意的值来做动画。
关键帧动画起源于传动动画。它的主要思想是指主导的动画在显著改变发生时重绘当前帧,也就是关键帧。而关键帧与关键帧之间的绘制则由Core Animation通过推算来完成。
我们来使用一个例子,通过关键帧动画来修改图层的颜色,代码如下:
class ViewController: UIViewController, CAAnimationDelegate{let colorLayer = CALayer()override func viewDidLoad() {super.viewDidLoad()colorLayer.frame = CGRect(x: 50, y: 100, width: 100, height: 100)colorLayer.backgroundColor = UIColor.red.cgColorself.view.layer.addSublayer(colorLayer)}override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {let animation = CAKeyframeAnimation()animation.keyPath = "backgroundColor"animation.duration = 2.0animation.values = [UIColor.red.cgColor, UIColor.green.cgColor, UIColor.blue.cgColor,UIColor.yellow.cgColor]animation.keyTimes = [0.0, 0.25, 0.5, 0.75, 1.0]colorLayer.add(animation, forKey: nil)}
效果如下:
CAKeyframeAnimation还有另外一种方式来指定动画,就是使用CGPath。path属性可以用一种更直观的方式来描述动画,我们这次采用UIKit提供的UIBezierPath来创建path,并且为了更直观我们使用CAShapeLayer将path渲染出来。然后我们在设置CAKeyframeAnimation来创建我们的动画。
代码如下:
let bezierPath = UIBezierPath()bezierPath.move(to: CGPoint(x: 50, y: 150))bezierPath.addCurve(to: CGPoint(x: 300, y: 150), controlPoint1: CGPoint(x: 150, y: 50), controlPoint2: CGPoint(x: 200, y: 250))let shapeLayer = CAShapeLayer()shapeLayer.path = bezierPath.cgPathshapeLayer.fillColor = UIColor.clear.cgColorshapeLayer.strokeColor = UIColor.red.cgColorshapeLayer.lineWidth = 3.0self.view.layer.addSublayer(shapeLayer)let layer = CALayer()layer.frame = CGRect(x: 50 - 40, y: 150 - 40, width: 40, height: 40)layer.contents = UIImage(named: "a-meiguihuahuameigui")?.cgImageself.view.layer.addSublayer(layer)let animation = CAKeyframeAnimation()animation.keyPath = "position"animation.path = bezierPath.cgPathanimation.duration = 4.0layer.add(animation, forKey: nil)
效果如下:
还可以体验一下animation.rotationMode = .rotateAuto这个属性。
let animation = CAKeyframeAnimation()animation.keyPath = "position"animation.path = bezierPath.cgPathanimation.duration = 4.0animation.rotationMode = .rotateAutolayer.add(animation, forKey: nil)
你会发现,花会沿着曲线运动时会发生旋转,使自己总垂直于曲线。
结语
本篇博客介绍了两个最常用的显式动画,属性动画和关键帧动画。大家在实际开发过程中应该经常会使用到。接下来的博客我们会继续讨论显式动画,进行动画的组合,过渡动画,以及取消动画。