配置为scrubbing的CAKeyframeAnimation如何实现平滑过渡至停止点?
优化方案:消除CAShapeLayer路径移动卡顿,实现平滑过渡
你的卡顿问题根源在于直接跳变timeOffset值——这相当于让图层瞬间从一个位置跳到另一个位置,没有任何过渡动画,自然会产生卡顿感。下面是两种针对性的优化方案:
方案1:给timeOffset添加过渡动画
不要直接赋值timeOffset,而是用CABasicAnimation让它从当前值平滑过渡到目标值,这样图层会沿着路径自然插值移动:
// 假设当前目标时间偏移量是 targetTimeOffset(= pathAnimation.beginTime + n) let currentOffset = layer.timeOffset let timeOffsetAnimation = CABasicAnimation(keyPath: "timeOffset") timeOffsetAnimation.fromValue = currentOffset timeOffsetAnimation.toValue = targetTimeOffset timeOffsetAnimation.duration = 0.2 // 可根据需求调整过渡时长 timeOffsetAnimation.fillMode = .forwards timeOffsetAnimation.isRemovedOnCompletion = false layer.add(timeOffsetAnimation, forKey: "timeOffsetTransition") // 同时更新模型层的值,避免动画结束后回弹 layer.timeOffset = targetTimeOffset
这个方法适合非实时连续控制的场景(比如点击按钮让图层跳到路径上的某个固定点),过渡时长可以根据交互需求调整,完全消除跳变卡顿。
方案2:用CADisplayLink平滑实时更新timeOffset
如果是实时连续控制(比如拖拽滑块、跟随手势移动),建议用CADisplayLink同步屏幕刷新频率来逐步更新timeOffset,确保每帧的偏移量变化均匀:
var displayLink: CADisplayLink? var currentProgress: Double = 0.0 // 当前路径进度(0~1对应动画duration的0~1) var targetProgress: Double = 0.0 // 目标路径进度 // 初始化CADisplayLink func setupDisplayLink() { displayLink = CADisplayLink(target: self, selector: #selector(updateLayerPosition)) displayLink?.add(to: .main, forMode: .common) } @objc func updateLayerPosition() { // 计算每帧需要移动的进度增量(用缓动函数可以让过渡更自然,比如ease-in-out) let progressDelta = (targetProgress - currentProgress) * 0.1 currentProgress += progressDelta // 当进度接近目标值时停止增量,避免抖动 if abs(currentProgress - targetProgress) < 0.001 { currentProgress = targetProgress } // 更新timeOffset layer.timeOffset = pathAnimation.beginTime + currentProgress * pathAnimation.duration } // 调用这个方法设置目标位置(比如用户拖拽时更新targetProgress) func setTargetPosition(progress: Double) { targetProgress = max(0, min(1, progress)) // 限制在0~1范围内 }
这种方法能保证每帧的移动量平滑均匀,完全贴合屏幕刷新节奏,不会出现卡顿或跳帧。
额外注意事项
- 原代码中
layer.speed = 0是正确的,但要确保你操作的是添加了动画的shapeLayer,而不是父layer(你的代码里写的是layer.speed = 0,如果layer是父层,建议改为shapeLayer.speed = 0,避免影响其他子图层)。 - 如果你不需要保留原动画的其他特性,也可以直接用
UIView.animate结合UIBezierPath的point(at:)方法,直接计算路径上的点并做帧动画,这种方式更直观,也能保证平滑:UIView.animate(withDuration: 0.2) { let targetPoint = bezierCurve.cgPath.point(at: targetProgress) shapeLayer.position = targetPoint }
内容的提问来源于stack exchange,提问作者clearlight
相关产品推荐
相关产品推荐

