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

时间:2017-12-20 23:43:20

标签: ios animation core-animation cadisplaylink catransaction

问题

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

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

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

full screen recording

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

slow motion zoomed screen recording

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

问题限制:

  • 此动画的一个关键目标是在每个音符上滚动平滑的线性,其开始和结束与每个音符头到达蓝线完全相同。蓝线代表伴奏音乐中的当前时间点。

  • 滚动持续时间和距离会有所不同,这些值会在滚动过程中动态生成,因此在执行期间对滚动速率进行硬编码将无效。

尝试解决方案

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

    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
        }
    }
    

1 个答案:

答案 0 :(得分:0)

使CAT交易重叠

这看起来像是一个黑客,但它修复了抖动。

如果CATransaction应在[{1}}秒的时间内将原点移动x,则可以将动画设置为y1.1 * x秒。滚动速率相同,但第二个CATransaction在第一个完成之前开始,抖动消失。

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

1.1 * y

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

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