问题描述
编辑:我稍微重构了问题并解决了部分问题,现在问题归结为为什么在恢复动画时表示层会出现故障/闪烁。在这一点上,我接受任何可以使动画随意向前和向后恢复而没有问题的答案。我不确定我使用的方法是否正确,我对 Swift 还是很陌生。
注意:底部的示例项目,用于更好地理解问题。
在我的项目中,我通过将图层 CABasicAnimation
属性设置为 .speed
来暂停 0
,然后我通过设置图层的 {{1}每当用户滚动滑块时,} 属性就等于 .timeOffset
UiSlider
属性。按代码:
.value
然后当用户滑动时:
layer.speed = 0
现在我想在滑块上的用户手势结束时随意向后或向前恢复动画,因此从与当前动画值相关的起点开始。我发现运行平稳的唯一可行解决方案如下,但它只能向前工作:
layer.timeOffset = CFTimeInterval(sender.value)
然后我可以在动画完成时再次暂停它:
let pausedTime = layer.timeOffset
layer.speed = 1.0
layer.timeOffset = 0.0
layer.beginTime = 0.0
let timeSincePause = layer.convertTime(CACurrentMediaTime(),from: nil) - pausedTime
layer.beginTime = timeSincePause
根据我的理解, dispatchQueue.main.asyncAfter(deadline: .Now()+1) {
layer.timeOffset = 0
layer.speed = 0
}
不仅定义了动画的实际速度,结合 .speed
属性,还定义了动画的方向:如果我将图层的速度设置为等于 {{1 }} 然后动画向后完成。参考关于 .duration
如何工作的 this 答案,我试图更改上面代码片段的参数以恢复动画向后倒退,但没有运气。我认为这会奏效:
-1
但是图层从来没有像这样动画过。该问题似乎与 camediatiming
方法有关。
然后我发现 this 问题与我的基本相同,唯一的答案有一个不错的解决方案。重构一下代码,我只能说:
let pausedTime = layer.timeOffset
layer.timeOffset = 0.0
layer.beginTime = 0.0
let timeSincePause = layer.convertTime(CACurrentMediaTime(),from: nil) - pausedTime
layer.beginTime = timeSincePause
layer.timeOffset = pausedTime*2
layer.speed = -1.0
然而,当动画向后播放时会出现问题,特别是表示层在恢复和完成时都会闪烁。我尝试了各种解决方案但没有运气,我做出了一些推测:
- 这可能是与
convertTime
相关的问题,因为我可以将其设置为layer.beginTime = CACurrentMediaTime() layer.speed = -1 dispatchQueue.main.asyncAfter(deadline: .Now()+1) { layer.timeOffset = 0 layer.speed = 0 }
或camediatimingFillMode
,但是当它恢复时,动画既不是最终状态也不是初始状态,因此不渲染初始帧; - 这是因为我没有保持模态树和表示树同步。
然而,当它在恢复和完成时闪烁/闪烁时,这两个都没有解释。此外,在我看来,动画在向前恢复时的持续时间可能为 1,但在向后恢复时只有 1-timeOffset,不确定。
真的不确定实际问题是什么以及如何解决这个烂摊子。我们非常欢迎所有建议。
对于任何感兴趣的人,这里有一个类似于我的示例项目,其灵感来自另一个问题(动画向前运行,向后运行并捕获故障只需调用 .back
)。我知道应该重构代码,但仍然可以达到目的。只需复制、粘贴和运行:
.forwards
解决方法
我设法消除了示例项目中 resumeLayerBackwards(layer:)
的故障。
实际上有两个问题:
- 动画视觉完成后出现一个空白屏幕
- 空白屏幕显示
1 - .timeOffset
秒
所以,问题似乎在于动画实际上不仅在 .timeOffset
期间播放,而且在整个 .duration
期间播放。出现空屏幕是因为没有为 1 - .timeOffset
块定义动画。
回想一下:CALayer
也采用 CAMediaTiming
协议,就像 CAAnimation
一样(定义了所有属性:尽管其中一些似乎不太清楚如何应用于层)。
在 speed = -1
秒过后 .timeOffset
— 属性 .timeOffset
变为零。这意味着动画已经开始,因此(以负速度)它完成了。虽然它不是那么明显 - 似乎因为 .fillMode
属性而被删除。为了解决这个问题,我在 perspectiveLayer.fillMode = .forwards
方法中添加了 animate()
。
要让动画恰好在 .timeOffset
秒后完成而不是整个 .duration
— 使用 .repeatDuration
属性。我已将 layer.repeatDuration = layer.timeOffset
添加到您的 resumeLayerBackwards(layer:)
方法中。
该项目仅适用于添加了两行。
我不能说这个解决方案对我来说真的合乎逻辑,尽管它有效。负速度对我来说有点不可预测。在我的项目中,我曾经通过交换克隆动画对象中的开始和结束值来反转动画。