You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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),可以通过计算动画的运行进度来推导当前旋转角度,这种方法不需要依赖呈现层,但精度略低(如果动画有缓动效果会不准确)。

具体思路:

  1. 用Timer或者CADisplayLink跟踪动画的运行时间
  2. 计算进度:已运行时间 / 总duration
  3. 用进度乘以总旋转角度得到当前角度,判断是否达到间隔

这个方案仅适合简单线性动画,优先推荐方案一,因为它能适配任何动画曲线,精度更高。


为什么你的KVO尝试失败?

  • 你观察的是CAKeyframeAnimation的values属性:这是动画的初始配置,一旦动画启动就不会再更新,所以KVO回调永远不会触发。
  • 如果你尝试观察模型层的transform:动画运行时,模型层的属性会保持初始值,动画是在呈现层上执行的,所以也得不到实时状态。

内容的提问来源于stack exchange,提问作者nayem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 13:12:47