[英]Why does animation jitter when CATransactions begin and end at about the same time?
How can I fix the jitter in my scrolling animation? 如何解决滚动动画中的抖动?
As seen in the animations below, there is a brief jitter every time the notes (black ovals) reach the vertical blue line, which makes it appear that the notes went backwards for a split second. 如下面的动画所示,每次音符(黑色椭圆形)到达垂直蓝线时都会出现短暂的抖动,这使得音符向后移动了一秒钟。
The scrolling animations are triggered by a series of CATransactions, and the jitter occurs every time one scrolling animation completes and another starts. 滚动动画是由一系列CATransactions触发的,每当一个滚动动画完成而另一个滚动动画开始时,就会发生抖动。
In the slow motion video below, it looks like there are actually two ovals on top of each other, one which stops and fades out while the other keeps scrolling. 在下面的慢动作视频中,看起来好像实际上彼此重叠有两个椭圆,一个椭圆停止并逐渐消失,而另一个继续滚动。 But, the code does not actually create one oval atop another.
但是,该代码实际上并没有在另一个椭圆上创建一个椭圆。
The videos (gifs) are from an iPhone SE screen recording, not a simulator. 视频(gif)来自iPhone SE屏幕录像,而不是模拟器。
Problem Constraints: 问题约束:
A key objective of this animation is to have smooth, linear scrolling across each note, that begins and ends exactly as each note head reaches the blue line. 该动画的主要目标是在每个音符之间进行平滑, 线性的滚动,并在每个音符头到达蓝线时准确地开始和结束。 The blue line represents the current point in time in accompanying music.
蓝线代表伴随音乐的当前时间点。
The scrolling durations and distances will vary, and these values are generated dynamically during the scrolling, so hard coding the scroll rate for the duration of execution will not work. 滚动持续时间和距离会有所不同,并且这些值是在滚动过程中动态生成的,因此在执行期间硬编码滚动速率将不起作用。
Attempted Solutions 尝试的解决方案
isScrolling
flag, to prevent new animations from starting before previous animations have completed, did not fix the jitter. isScrolling
标志(以防止新动画在以前的动画完成之前开始)不能解决抖动问题。 A StaffLayer
(defined below) controls the scrolling: StaffLayer
(定义如下)控制滚动:
.scrollAcrossCurrentChordLayer()
manages the CATransaction
. .scrollAcrossCurrentChordLayer()
管理CATransaction
。 This method is called by the .scrollTimer
CADisplayLink
.scrollTimer
CADisplayLink
调用此方法。
.start()
and .scrollTimer
manage the CADisplayLink
.scrollTimer
.start()
和.scrollTimer
管理CADisplayLink
Code heavily abbreviated for clarity 为清楚起见,代码严重缩写
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
}
}
This seems like a hack, but it fixes the jitter. 这似乎是一种破解,但它可以解决抖动问题。
If the CATransaction should shift origin by x
over a period of y
seconds, you can set the animation to go 1.1 * x
over a period of 1.1 * y
seconds. 如果CATransaction应转向起源
x
一段y
秒,你可以设置动画去1.1 * x
在一段1.1 * y
秒。 The the scroll rate is the same, but the second CATransaction starts before the first one has finished, and the jitter disappears. 滚动速率相同,但是第二个CATransaction在第一个完成之前开始,并且抖动消失。
This can be achieved by a small modification of the original code: 这可以通过对原始代码进行少量修改来实现:
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()
I can't give a rigorous explanation for why this works. 我不能对此做一个严格的解释。 It may be related to the optimizations happening behind the scenes in CoreAnimation.
这可能与CoreAnimation中幕后发生的优化有关。
A drawback is that the overlap can interfere with subsequent animations if the overlap is too large, so this is not a good general purpose solution, just a hack. 缺点是,如果重叠太大,则重叠会干扰后续的动画,因此,这不是一个好的通用解决方案,只是一个hack。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.