我一直在研究 iOS 10 中非常酷的新
UIViewPropertyAnimator
类。它可以让你轻松地 执行诸如暂停、恢复和反转飞行中 UIView 动画之类的操作。过去,您必须操作系统创建的底层 CAAnimations 才能对 UIView 动画执行此操作。
新的
UIViewPropertyAnimator
类有一个属性 fractionComplete
。它从 0.0(动画开始)到 1.0(动画结束)变化。如果更改该值,您可以从头到尾“拖动”动画。
我在 Github 上有一个名为 KeyframeViewAnimations 的演示项目,它允许您使用相当神秘的 CAAnimation 技巧使用滑块来回滑动 UIView 和 CAAnimations。当动画运行时,滑块从开始移动到结束以显示动画的进度。该项目是在 Swift 发布之前用 Objective-C 编写的,但底层技术在 Objective-C 或 Swift 中是相同的。
我决定使用
UIViewPropertyAnimator
创建一个等效的项目。虽然有一些怪癖,但相当简单。我不知道如何干净地做的一件事是观察动画的 fractionComplete
属性的进度,以便我可以随着动画的进展更新滑块。我尝试在 fractionComplete
属性上设置 KVO(键值观察),但在动画完成之前它不会触发 KVO 通知。
我最终做的是设置一个以非常小的间隔运行的计时器,并重复查询
UIViewPropertyAnimator
的 fractionComplete
属性,并随着进度更新滑块。它有效,但不是一种非常干净的做事方式。如果有一种方法可以通知动画的进度那就更好了。
我希望我遗漏了一些东西。
您可以通过此链接在 Github 上查看基于
UIViewPropertyAnimator
的项目:UIViewPropertyAnimator-test。
从动画师获取进度值的代码是这样的代码:
func startTimer(_ start: Bool) {
if start {
timer = Timer.scheduledTimer(withTimeInterval: 0.02,
repeats: true) {
timer in
var value = Float(self.viewAnimator.fractionComplete)
if self.animatingBackwards {
value = 1 - value
}
self.animationSlider.value = value
}
}
else {
self.timer?.invalidate()
}
}
我可能应该将上面的代码转换为使用
CADisplayLink
计时器而不是普通计时器(NSTimer),但我希望有更好的方法。
仅供参考,经过研究后,答案似乎是不,随着动画的进展,没有办法获取更新。您必须设置一个计时器(
Timer
或CADisplayLink
)并在每次触发时更新您的进度值。
您是否尝试子类化 UIViewPropertyAnimator 然后覆盖fractionComplete 属性?
class MyPropertyAnimator : UIViewPropertyAnimator {
override var fractionComplete: CGFloat {
didSet {
print("didSetFraction")
}
}
}
这是一个
UIViewPropertyAnimator
子类,它使用 CADisplayLink
将更新发布到 onFractionCompleteUpdated
回调。
let animator = MonitorableUIViewPropertyAnimator(duration: 0.45, dampingRatio: 0.8) {
// Animation Code
}
animator.onFractionCompleteUpdated = { fractionComplete in
print("Animation Fraction Complete: \(fractionComplete)")
}
/// A animator that allows you to monitor fractionComplete via `onFractionCompleteUpdated`.
/// Useful when a animation has key events that need to happen but the events themselves are not animatable.
/// Example: You need to change the zIndex of two views half way through.
class MonitorableUIViewPropertyAnimator : UIViewPropertyAnimator {
private var displayLink: CADisplayLink?
private var lastFractionComplete: CGFloat?
/// Called everytime `self.fractionComplete` has a new value.
var onFractionCompleteUpdated: ((CGFloat) -> ())? = nil {
didSet {
if onFractionCompleteUpdated != nil {
if displayLink == nil {
displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
displayLink?.add(to: .main, forMode: .common)
// Clean up when the animation completes
addCompletion { [weak self] _ in
self?.displayLink?.invalidate()
}
}
} else {
// No longer need display link - clean up
displayLink?.invalidate()
}
}
}
@objc private func handleDisplayLink(_ displayLink: CADisplayLink) {
// Don't call if the animation is paused or not moving
if lastFractionComplete != fractionComplete {
onFractionCompleteUpdated?(fractionComplete)
lastFractionComplete = fractionComplete
}
// If the animation stops outside of a normal completion - clean up
if state == .stopped {
displayLink.invalidate()
}
}
}