显式动画与隐式动画
显示动画是你主动使用CAAnimation进行动画的方式,而隐式动画则不是。
相比隐式动画,显示动画里你可以更细节的控制动画的走向,并且隐式动画结束以后视图的位置会停留在最后时刻。
表现层
首先我们来做一个动画
UIView.animate(withDuration: 5, animations: {
self.moveView.frame.origin.y += 200
})
这个动画是让moveView在Y轴上移动200个点。
- 我们在动画代码前,打印一下这个控件的frame信息 (155.0, 140.0, 64.0, 64.0)
- 然后我们在动画开始2秒和动画结束时打印一下frame信息,让人疑惑的是结果都为 (155.0, 340.0, 64.0, 64.0)
- 我们先停一下,去看看CoreAnimation
使用CA动画时,主要是使用的CAAnimation的子类(下面简称 anim )。
anim 是添加到Layer上的,你可以在layer上访问添加到该layer的 anim 。
好的,现在我们做一个和上面一样的Y轴上的位移动画。
- 在动画开始后的2秒打印一下frame和layer.frame信息,都是 (155.0, 140.0, 64.0, 64.0)
- 在2秒时打印animationKeys, Optional([“position”])
- 动画结束后,控件恢复到了原状,animationKeys为 nil
- 我们把anim改成动画结束后保持结束时状态再看看1-3点的信息
- frame和layer.frame信息,也还都是 (155.0, 140.0, 64.0, 64.0)
- 在2秒时打印animationKeys, Optional([“position”])
- 动画结束后,控件恢复到了原状,animationKeys为 Optional([“position”])
- 此时我们使用Xcode的视图调试工具查看,控件位置在 (155.0, 140.0, 64.0, 64.0) ,并且调试界面的位置和APP上真实显示的位置完全不一样。
这是因为CoreAnimation在做动画的时候使用了模型层和表现层两种层级。在动画过程中,模型层会隐藏,并且复制出一模一样的Layer作为表现层。 CoreAnimation会拿表现层做动画,这样就不会影响原有的图层。 使用 presentation 属性可以访问到表现层,这样就能实时的拿到真实看见的动画的属性了。
- 现在我们根据刚才的 anim 动画查看一下 presentation 的信息。
- 果然,控件位置的实时信息是反应在这个layer上的
隐式动画的秘密
我们在动画进行时打印 presentation 的信息,打印的frame信息确实能够正确反应当前控件的位置情况。
我们再打印 animationKeys 信息看看, Optional([“position”]) 。
我想答案已经呼之欲出了。 隐式动画 使用的是 CAAnimation 在做动画,只是细节对程序员隐藏了。
显式动画的结束
在隐式动画中,我们只需要关心视图的什么属性从A到B,而在显式动画中还要考虑动画结束后表现层被销毁的问题。
如果我们想要保持视图在动画结束时的样子,简单一点的做法是设置removedOnCompletion为false,但是这种方法,只是保证了表现层不会被销毁,模型层还停留在原来的位置。
如果想要让模型层也改变,只需要在动画添加到视图之后直接改变视图的属性即可,因为此时视图已经开始做动画,改为表现层显示在界面上了。
CAAniamtion的Delegate
对于这个Delegate,唯一需要注意的是,它是strong修饰的,并且会在CAAnimation被销毁后销毁。
所以推荐用法是使用一个实现了该Delegate协议的Class,如下:
public class Target: NSObject, CAAnimationDelegate {
public typealias StartHandler = (_ anim: CAAnimation) -> Swift.Void
public typealias StopHandler = (_ anim: CAAnimation, _ finished: Bool) -> Swift.Void
let begin: StartHandler?
let end: StopHandler?
init(begin: StartHandler?, end: StopHandler?) {
self.begin = begin
self.end = end
}
public func animationDidStart(_ anim: CAAnimation) {
begin?(anim)
}
public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
end?(anim, flag)
}
}
CAAnimationGroup
Group可以把多个动画集中起来管理。然而需要注意的地方在于每个动画的时间间隔。
设置beginTime
animation.beginTime = CACurrentMediaTime() + offset
正常情况下,我们需要设置的时间偏移为当前时间+偏移时间。当时当我们这样设置好一个Animation后,又需要将这个Animation放入一个Group里时,会发现动画没有按照预期进行。
因为Group里的每一个Animation的beginTime时间计算为group.beginTime + animation.beginTime。
所以当你要把一个Animation放入Group前,一定要计算好时间偏移。
UKAnimation
这里安利一下我的CoreAnimation库。你可以只用几行代码轻松的创建一个Animation或者是Group,不需要考虑太多。
/// 像这样的简单动画
UKAnimation(animView).shakeR().run()
/// 亦或者复杂的组合动画
UKAnimation(animView)
.move(to: [100,100]).stay()
.fade(from: 1, to: 0).modify{$0?.autoreverses = true}.stay()
.move(to: [300,400]).after(begin: 1, willGroup: true).stay()
.shakeR(radian:10, times:4, duration:0.5).after(begin: 1.5, willGroup: true)
.group().duration(2).modify{$0?.autoreverses = true}
.run()
/// 上面是调用的几个内置的简单动画
/// 你也可以使用自己想要的动画
UKAnimation(animView).add(YourAnim()).run()
UKAnimation(animView).add { return /* your animation */}.run()