为什么CAT交易几乎同时开始和结束时动画抖动?

问题描述 投票:0回答:1

Problem

如何在滚动动画中修复抖动?

如下面的动画所示,每次音符(黑色椭圆形)到达垂直蓝线时都会有一个短暂的抖动,这使得音符看起来向后劈了一秒钟。

滚动动画由一系列CATransactions触发,每当一个滚动动画完成而另一个开始动画时,就会发生抖动。

full screen recording

在下面的慢动作视频中,看起来实际上有两个椭圆形在彼此的顶部,一个停止并淡出,而另一个保持滚动。但是,代码实际上并没有在另一个上面创建一个椭圆。

slow motion zoomed screen recording

视频(GIF)来自iPhone SE屏幕录制,而不是模拟器。

问题限制:

  • 这个动画的一个关键目标是在每个音符上进行平滑,线性的滚动,在每个音符头到达蓝线时开始和结束。蓝线表示伴奏音乐中的当前时间点。
  • 滚动持续时间和距离将变化,并且这些值在滚动期间动态生成,因此在执行期间对滚动速率进行硬编码将不起作用。

试图解决方案

  1. 设置isScrolling标志,以防止在之前的动画完成之前启动新动画,并未修复抖动。
  2. 将滚动开始时间设置为稍早发生(即1或2次屏幕重绘的长度),也不起作用。
  3. 一起做1和2稍微改善了问题,但没有解决它。

Code Snippet

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
    }
}
ios animation core-animation cadisplaylink catransaction
1个回答
0
投票

Make the CATransactions overlap

这似乎是一个黑客,但它修复了抖动。

如果CATransaction应该在x秒的时间内通过y移动原点,则可以将动画设置为在1.1 * x秒的时间段内移动1.1 * y。滚动速率是相同的,但第二个CATransaction在第一个完成之前开始,抖动消失。

这可以通过对原始代码进行少量修改来实现:

let overlapFactor = 1.1
CATransaction.setAnimationDuration(
        (chordLayer.chord.lengthInSeconds ?? 0.0)
        * overlapFactor // <- ADDED OVERLAP FACTOR HERE
)
bounds.origin.x += distance*CGFloat(overlapFactor) // <- ADDED OVERLAP FACTOR HERE
CATransaction.commit()

我无法对其原因进行严格解释。它可能与CoreAnimation中幕后发生的优化有关。

缺点是如果重叠太大,重叠可能会干扰后续动画,因此这不是一个好的通用解决方案,只是一个黑客攻击。

© www.soinside.com 2019 - 2024. All rights reserved.