如何通过CAKeyframeAnimation检测CALayer的特定旋转进度?
解决CAKeyframeAnimation旋转时每X度触发任务的问题
你的问题很典型——CAKeyframeAnimation运行时,模型层的属性不会实时更新,而且你观察的动画对象values是静态配置属性,运行时根本不会变化,所以KVO完全不会触发。下面给你两个靠谱的解决方案,优先推荐第一个,因为它能精准获取动画的实时状态:
方案一:用CADisplayLink监听呈现层的实时旋转角度
动画运行时,真正展示在屏幕上的是图层的呈现层(presentationLayer),它会实时反映动画的中间状态,而模型层的属性保持不变。我们可以用CADisplayLink(和屏幕刷新率同步的定时器)来每帧检查呈现层的旋转角度,当达到指定的X度间隔时执行任务。
具体代码实现:
首先定义几个变量来记录状态:
// 记录上一次触发任务的累计角度,避免重复触发 private var lastTriggeredTotalDegrees: CGFloat = 0.0 // 你需要的触发间隔(X度) private let triggerIntervalDegrees: CGFloat = 45.0 // 替换成你的X值 private var rotationDisplayLink: CADisplayLink?
然后添加动画并启动CADisplayLink:
// 你的原有动画代码 let totalRotationDegrees: CGFloat = 7 * 360.0 // 7次完整旋转 let rotationAnimation = CAKeyframeAnimation(keyPath: "transform.rotation.z") rotationAnimation.values = [0, totalRotationDegrees * (CGFloat.pi / 180)] rotationAnimation.keyTimes = [0, 1] rotationAnimation.duration = 15 rotationAnimation.delegate = self // 用来在动画结束时停止监听 myLayer.add(rotationAnimation, forKey: "transformationAnimation") // 启动CADisplayLink rotationDisplayLink = CADisplayLink(target: self, selector: #selector(checkCurrentRotation)) rotationDisplayLink?.add(to: .main, forMode: .common)
接下来实现核心的检查方法和动画代理:
@objc private func checkCurrentRotation() { // 获取图层的呈现层,确保动画在运行中 guard let presentationLayer = myLayer.presentation() as? CALayer else { return } // 获取当前累计旋转的弧度,转成角度 let currentRadians = presentationLayer.value(forKeyPath: "transform.rotation.z") as? CGFloat ?? 0.0 let currentTotalDegrees = currentRadians * (180.0 / CGFloat.pi) // 计算当前角度与上次触发角度的差值,达到间隔就执行任务 if abs(currentTotalDegrees - lastTriggeredTotalDegrees) >= triggerIntervalDegrees { // 这里执行你的自定义任务 print("触发任务!当前累计旋转角度:\(currentTotalDegrees)°") // 更新上次触发的角度,避免重复触发 lastTriggeredTotalDegrees = currentTotalDegrees } } // 实现CAAnimationDelegate,动画结束后停止监听 extension YourViewController: CAAnimationDelegate { func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { if anim == myLayer.animation(forKey: "transformationAnimation") { rotationDisplayLink?.invalidate() rotationDisplayLink = nil lastTriggeredTotalDegrees = 0.0 // 重置状态 } } }
方案二:基于动画进度计算角度(仅适用于线性动画)
如果你的动画是线性速度(没有自定义的timingFunction),可以通过计算动画的运行进度来推导当前旋转角度,这种方法不需要依赖呈现层,但精度略低(如果动画有缓动效果会不准确)。
具体思路:
- 用
Timer或者CADisplayLink跟踪动画的运行时间 - 计算进度:
已运行时间 / 总duration - 用进度乘以总旋转角度得到当前角度,判断是否达到间隔
这个方案仅适合简单线性动画,优先推荐方案一,因为它能适配任何动画曲线,精度更高。
为什么你的KVO尝试失败?
- 你观察的是
CAKeyframeAnimation的values属性:这是动画的初始配置,一旦动画启动就不会再更新,所以KVO回调永远不会触发。 - 如果你尝试观察模型层的
transform:动画运行时,模型层的属性会保持初始值,动画是在呈现层上执行的,所以也得不到实时状态。
内容的提问来源于stack exchange,提问作者nayem
相关产品推荐
相关产品推荐

