在 CAAnimation 期间修改值

Modify a value during a CAAnimation

假设你有一个简单的动画

    let e : CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd")
    e.duration = 2.0
    e.fromValue = 0 
    e.toValue = 1.0
    e.repeatCount = .greatestFiniteMagnitude
    self.someLayer.add(e, forKey: nil)

一层

private lazy var someLayer: CAShapeLayer

很容易读取每个动画步骤的 strokeEnd 值。

只需更改为自定义图层

private lazy var someLayer: CrazyLayer

然后

class CrazyLayer: CAShapeLayer {
    override func draw(in ctx: CGContext) {
        print("Yo \(presentation()!.strokeEnd)")
        super.draw(in: ctx)
    }
}

实际上能够在每一步设置图层的 属性 值会非常方便。

示例:

class CrazyLayer: CAShapeLayer {
    override func draw(in ctx: CGContext) {
        print("Yo \(presentation()!.strokeEnd)")
        strokeStart = presentation()!.strokeEnd - 0.3
        super.draw(in: ctx)
    }
}

当然你不能那样做 -

attempting to modify read-only layer

也许你可以有一个自定义动画 属性

class LoopyLayer: CAShapeLayer {
    @NSManaged var trick: CGFloat

    override class func needsDisplay(forKey key: String) -> Bool {
        if key == "trick" { return true }
        return super.needsDisplay(forKey: key)
    }

    ... somehow for each frame
    strokeEnd = trick * something
    backgroundColor = alpha: trick
}

如何"manually"设置每帧动画属性的值?

是否有一种方法可以简单地提供(覆盖?)一个计算每帧值的函数?如何通过计算其他属性或其他自定义设置某些属性的每一帧的值 属性?

(脚注 1 - 一种变通方法是滥用关键帧动画,但这不是重点。)

(脚注 2 - 当然,通常最好使用 CADisplayLink 以普通的旧手动方式简单地制作动画,而不用担心 CAAnimation,但这个 QA 是关于 CAAnimation 的。)

粗略地说,CoreAnimation让你可以根据一定的时间函数在一定的值之间进行插值。

CAAnimation 有一个 private method 在给定时间将其插值应用于图层。

在模型层 class(init(layer:) 调用)的实例中创建表示层之后,在 [= 的堆栈中将该值注入表示层15=] 方法调用在 draw(in ctx: CGContext) 方法调用之前。

因此,假设表示层的 class 与自定义层的 class 相同,一种解决方法是让动画 属性 不同于 属性 用于渲染层的内容。您可以为 interpolatedValue 属性 制作动画,并让另一个动态 属性 strokeEnd 动态计算出 interpolatedValue 值中每一帧的适当值。

在极端情况下,interpolatedValue 可能只是从 0 到 1 的动画进度,基于此您可以动态计算表示层中的 strokeEnd 值。您甚至可以向您的代表询问给定进度的价值:

@objc @NSManaged open dynamic var interpolatedValue: CGFloat

open override class func needsDisplay(forKey key: String) -> Bool {
    var result = super.needsDisplay(forKey: key)
    result = result || key == "interpolatedValue"
    return result
}
 
open var strokeEnd : CGFloat {
    get {
       return self.delegate.crazyLayer(self, strokeEndFor: self.interpolatedValue)
    }
    set {
        ...
    }
}

let animation = CABasicAnimation(keyPath: "interpolatedValue")
animation.duration = 2.0
animation.fromValue = 0.0
animation.toValue = 1.0
crazyLayer.add(animation, forKey: "interpolatedValue")

另一种解决方法是使用 CAKeyframeAnimation,您可以在其中指定必须在相应时间将哪个值分配给动画 属性。

如果您可能想为每一帧分配一个内插分量的复数值,我有一个 project where I do that for animation of NSAttributedString value by interpolation of its attributes, and the article 描述方法的地方。