如何在滚动动画中修复抖动?
如下面的动画所示,每次音符(黑色椭圆形)到达垂直蓝线时都会有一个短暂的抖动,这使得音符看起来会向后退一秒钟。
滚动动画由一系列CATransactions触发,每次滚动动画完成而另一个动画完成时,抖动就会发生。
在下面的慢动作视频中,看起来实际上有两个椭圆形在彼此的顶部,一个停止并淡出而另一个继续滚动。但是,代码实际上并没有在另一个上面创建一个椭圆。
视频(GIF)来自iPhone SE屏幕录制,而不是模拟器。
问题限制:
此动画的一个关键目标是在每个音符上滚动平滑的线性,其开始和结束与每个音符头到达蓝线完全相同。蓝线代表伴奏音乐中的当前时间点。
滚动持续时间和距离会有所不同,这些值会在滚动过程中动态生成,因此在执行期间对滚动速率进行硬编码将无效。
尝试解决方案
isScrolling
标志,以防止在之前的动画完成之前启动新动画,但未修复抖动。 StaffLayer
(在下面定义)控制滚动:
.scrollAcrossCurrentChordLayer()
管理CATransaction
。此方法由.scrollTimer
CADisplayLink
.start()
和.scrollTimer
管理CADisplayLink
代码严格缩写为清晰
class StaffLayer: CAShapeLayer, CALayerDelegate {
var currentTimePositionX: CGFloat // x-coordinate of blue line
var scrollTimer: CADisplayLink? = nil
/// Sets and starts `scrollTimer`, which is a `CADisplayLink`
func start() {
scrollTimer = CADisplayLink(
target: self,
selector: #selector(scrollAcrossCurrentChordLayer)
)
scrollTimer?.add(
to: .current,
forMode: .defaultRunLoopMode
)
}
/// Trigger scrolling when the currentChordLayer.startTime has passed
@objc func scrollAcrossCurrentChordLayer() {
// don't scroll if the chord hasn't started yet
guard currentChordLayer.startTime < Date().timeIntervalSince1970 else { return }
// compute how far to scroll
let nextChordMinX = convert(
nextChordLayer.bounds.origin,
from: nextChordLayer
).x
let distance = nextChordMinX - currentTimePositionX // distance from note to vertical blue line
// perform scrolling in CATransaction
CATransaction.begin()
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(
name: kCAMediaTimingFunctionLinear
))
CATransaction.setAnimationDuration(
chordLayer.chord.lengthInSeconds ?? 0.0
)
bounds.origin.x += distance
CATransaction.commit()
// set currentChordLayer to next chordLayer
currentChordLayer = currentChordLayer.nextChordLayer
}
}
答案 0 :(得分:0)
这看起来像是一个黑客,但它修复了抖动。
如果CATransaction应在[{1}}秒的时间内将原点移动x
,则可以将动画设置为y
秒1.1 * x
秒。滚动速率相同,但第二个CATransaction在第一个完成之前开始,抖动消失。
这可以通过对原始代码进行少量修改来实现:
1.1 * y
我无法对其原因进行严格解释。它可能与CoreAnimation中幕后发生的优化有关。
缺点是如果重叠太大,重叠可能会干扰后续动画,因此这不是一个好的通用解决方案,只是一个黑客。