所以我一直在研究自己的解决方案
CADisplayLink
. 文档就是这样描述的
Cadisplaylink公司
:
CadisplayLink是一个计时器对象,它允许应用程序
将其图形与显示的刷新率同步。
它基本上提供了执行绘图代码时的回调(以便可以顺利运行动画)。
我不打算在这个答案中解释所有的事情,因为这将是一个很大的代码,而且大部分应该是清楚的。如果有什么不清楚的地方或者你有问题,你可以在这个答案下面发表评论。
此解决方案使动画完全自由,并提供协调动画的能力。我看了很多
Animator
类,并希望使用类似的语法,以便我们可以轻松地将动画从Android移植到iOS或其他方式。我已经测试了几天了,也去掉了一些怪癖。但是说够了,让我们看看代码!
这就是
动画师
类,这是动画类的基本结构:
class Animator {
internal var displayLink: CADisplayLink? = nil
internal var startTime: Double = 0.0
var hasStarted: Bool = false
var hasStartedAnimating: Bool = false
var hasFinished: Bool = false
var isManaged: Bool = false
var isCancelled: Bool = false
var onAnimationStart: () -> Void = {}
var onAnimationEnd: () -> Void = {}
var onAnimationUpdate: () -> Void = {}
var onAnimationCancelled: () -> Void = {}
public func start() {
hasStarted = true
startTime = CACurrentMediaTime()
if(!isManaged) {
startDisplayLink()
}
}
internal func startDisplayLink() {
stopDisplayLink() // make sure to stop a previous running display link
displayLink = CADisplayLink(target: self, selector: #selector(animationTick))
displayLink?.add(to: .main, forMode: .commonModes)
}
internal func stopDisplayLink() {
displayLink?.invalidate()
displayLink = nil
}
@objc internal func animationTick() {
}
public func cancel() {
isCancelled = true
onAnimationCancelled()
if(!isManaged) {
animationTick()
}
}
}
它包含了所有的生命要素,比如启动
Cadisplaylink公司
,提供停止的能力
Cadisplaylink公司
(动画完成时),指示状态和某些回调的布尔值。您还会注意到ismanaged布尔值。这个布尔值是当
动画师
被一群人控制。如果是,则组将提供动画标记,而此类不应启动
Cadisplaylink公司
.
下一个是
ValueAnimator
:
class ValueAnimator : Animator {
public internal(set) var progress: Double = 0.0
public internal(set) var interpolatedProgress: Double = 0.0
var duration: Double = 0.3
var delay: Double = 0
var interpolator: Interpolator = EasingInterpolator(ease: .LINEAR)
override func animationTick() {
// In case this gets called after we finished
if(hasFinished) {
return
}
let elapsed: Double = (isCancelled) ? self.duration : CACurrentMediaTime() - startTime - delay
if(elapsed < 0) {
return
}
if(!hasStartedAnimating) {
hasStartedAnimating = true
onAnimationStart()
}
if(duration <= 0) {
progress = 1.0
} else {
progress = min(elapsed / duration, 1.0)
}
interpolatedProgress = interpolator.interpolate(elapsedTimeRate: progress)
updateAnimationValues()
onAnimationUpdate()
if(elapsed >= duration) {
endAnimation()
}
}
private func endAnimation() {
hasFinished = true
if(!isManaged) {
stopDisplayLink()
}
onAnimationEnd()
}
internal func updateAnimationValues() {
}
}
这个类是所有值动画师的基类。但如果你想自己计算的话,也可以用它来制作动画。你可能会注意到
Interpolator
和
interpolatedProgress
在这里。这个
插补器
稍后将显示类。这个类提供了动画的简化。这里就是
内插进度
进来。
progress
只是从0.0到1.0的线性进程,但是
内插进度
可能对宽松政策有不同的价值。例如,当
进步
值为0.2,
内插进度
可能已经有0.4基于你将使用的宽松。同时确保使用
内插进度
计算正确的值。一个例子和
值动画师
在下面。
下面是
CGFloatValueAnimator
顾名思义,它将设置cgfloat值的动画:
class CGFloatValueAnimator : ValueAnimator {
private let startValue: CGFloat
private let endValue: CGFloat
public private(set) var animatedValue: CGFloat
init(startValue: CGFloat, endValue: CGFloat) {
self.startValue = startValue
self.endValue = endValue
self.animatedValue = startValue
}
override func updateAnimationValues() {
animatedValue = startValue + CGFloat(Double(endValue - startValue) * interpolatedProgress)
}
}
这是一个如何对
值动画师
如果你需要像双倍或整数这样的其他函数,你可以做更多这样的函数。你只需要提供一个开始和结束值
动画师
基于
内插进度
什么电流
animatedValue
是。你可以用这个
动画值
更新您的视图。最后我会举一个例子。
因为我说过
插补器
已经几次了,我们会继续
插补器
现在:
protocol Interpolator {
func interpolate(elapsedTimeRate: Double) -> Double
}
这只是一个你可以自己实现的协议。我给你看一部分
EasingInterpolator
我用我自己。如果有人需要,我可以提供更多。
class EasingInterpolator : Interpolator {
private let ease: Ease
init(ease: Ease) {
self.ease = ease
}
func interpolate(elapsedTimeRate: Double) -> Double {
switch (ease) {
case Ease.LINEAR:
return elapsedTimeRate
case Ease.SINE_IN:
return (1.0 - cos(elapsedTimeRate * Double.pi / 2.0))
case Ease.SINE_OUT:
return sin(elapsedTimeRate * Double.pi / 2.0)
case Ease.SINE_IN_OUT:
return (-0.5 * (cos(Double.pi * elapsedTimeRate) - 1.0))
case Ease.CIRC_IN:
return -(sqrt(1.0 - elapsedTimeRate * elapsedTimeRate) - 1.0)
case Ease.CIRC_OUT:
let newElapsedTimeRate = elapsedTimeRate - 1
return sqrt(1.0 - newElapsedTimeRate * newElapsedTimeRate)
case Ease.CIRC_IN_OUT:
var newElapsedTimeRate = elapsedTimeRate * 2.0
if (newElapsedTimeRate < 1.0) {
return (-0.5 * (sqrt(1.0 - newElapsedTimeRate * newElapsedTimeRate) - 1.0))
}
newElapsedTimeRate -= 2.0
return (0.5 * (sqrt(1 - newElapsedTimeRate * newElapsedTimeRate) + 1.0))
default:
return elapsedTimeRate
}
}
}
这些只是一些特定测量的计算示例。实际上,我在这里移植了所有为安卓设计的版本:
https://github.com/MasayukiSuda/EasingInterpolator
.
在我展示一个例子之前,我还有一个类要展示。这是一个类,它允许对动画师进行分组:
class AnimatorSet : Animator {
private var animators: [Animator] = []
var delay: Double = 0
var playSequential: Bool = false
override func start() {
super.start()
}
override func animationTick() {
// In case this gets called after we finished
if(hasFinished) {
return
}
let elapsed = CACurrentMediaTime() - startTime - delay
if(elapsed < 0 && !isCancelled) {
return
}
if(!hasStartedAnimating) {
hasStartedAnimating = true
onAnimationStart()
}
var finishedNumber = 0
for animator in animators {
if(!animator.hasStarted) {
animator.start()
}
animator.animationTick()
if(animator.hasFinished) {
finishedNumber += 1
} else {
if(playSequential) {
break
}
}
}
if(finishedNumber >= animators.count) {
endAnimation()
}
}
private func endAnimation() {
hasFinished = true
if(!isManaged) {
stopDisplayLink()
}
onAnimationEnd()
}
public func addAnimator(_ animator: Animator) {
animator.isManaged = true
animators.append(animator)
}
public func addAnimators(_ animators: [Animator]) {
for animator in animators {
animator.isManaged = true
self.animators.append(animator)
}
}
override func cancel() {
for animator in animators {
animator.cancel()
}
super.cancel()
}
}
如你所见,这里是我设置
isManaged
布尔函数。你可以在这个类中放置多个你制作的动画来协调它们。因为这个类还扩展了
动画师
你也可以放另一个
AnimatorSet
或者是多重的。默认情况下,它会同时运行所有动画,但如果
playSequential
设置为true时,它将按顺序运行所有动画。
演示时间:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let animView = UIView()
animView.backgroundColor = UIColor.yellow
self.view.addSubview(animView)
animView.snp.makeConstraints { maker in
maker.width.height.equalTo(100)
maker.center.equalTo(self.view)
}
let translateAnimator = CGFloatValueAnimator(startValue: 0, endValue: 100)
translateAnimator.delay = 1.0
translateAnimator.duration = 1.0
translateAnimator.interpolator = EasingInterpolator(ease: .CIRC_IN_OUT)
translateAnimator.onAnimationStart = {
animView.backgroundColor = UIColor.blue
}
translateAnimator.onAnimationEnd = {
animView.backgroundColor = UIColor.green
}
translateAnimator.onAnimationUpdate = {
animView.transform.tx = translateAnimator.animatedValue
}
let alphaAnimator = CGFloatValueAnimator(startValue: animView.alpha, endValue: 0)
alphaAnimator.delay = 1.0
alphaAnimator.duration = 1.0
alphaAnimator.interpolator = EasingInterpolator(ease: .CIRC_IN_OUT)
alphaAnimator.onAnimationUpdate = {
animView.alpha = alphaAnimator.animatedValue
}
let animatorSet = AnimatorSet()
// animatorSet.playSequential = true // Uncomment this to play animations in order
animatorSet.addAnimator(translateAnimator)
animatorSet.addAnimator(alphaAnimator)
animatorSet.start()
}
}
我认为这其中的大部分都可以说明问题。我创建了一个视图来转换x并淡出。对于您实现的每个动画
onAnimationUpdate
回调以更改视图中使用的值,如本例中的转换x和alpha。
注:
与android相反,这里的持续时间和延迟以秒为单位,而不是以毫秒为单位。
我们现在正在使用这个代码,它工作得很好!我已经在我们的android应用程序中写了一些动画。我可以很容易地将动画移植到iOS,只需少量重写,动画的工作原理完全相同!我可以复制我问题中写的代码,将kotlin代码改为swift,应用
动画更新
,将持续时间和延迟更改为秒,动画的效果就像一个符咒。
我想把它作为一个开放源码库发布,但是我还没有这样做。我发布后会更新这个答案。
如果您对代码或它如何工作有任何疑问,请随时提出。