禁用 CALayer 遮罩帧变化动画

Disable CALayer mask frame change animations

我有一个 CAShapeLayer 实例,其中包含一个非零 CALayer mask。我正在尝试使用该面具的框架来剪裁形状。哪个工作正常。但是当我改变框架时,我不希望它为框架变化设置动画。我更新了视图 layoutSubviews 中的框架,我发现了一些有趣的事情:

override func layoutSubviews() {
    super.layoutSubviews()
    ...
    if let futureMask = self.futureShape.mask {
        "0.future position animation \(futureMask.animation(forKey: "position"))".print()
        futureMask.removeAllAnimations()
        futureMask.add(CAAnimation(), forKey: "position")
        futureMask.add(CAAnimation(), forKey: "bounds")
        "1.future position animation \(futureMask.animation(forKey: "position"))".print()
        let nowX = computeNowX()
        futureMask.frame = CGRect(x: box.left + nowX, y: box.top, width: box.width - nowX, height: box.height)
        "2.future position animation \(futureMask.animation(forKey: "position"))".print()
    }
}

这产生的输出看起来像:

0.future position animations Optional(<CABasicAnimation:0x174624ac0; duration = 0.25; fillMode = backwards; timingFunction = default; keyPath = position; fromValue = NSPoint: {418.94695306710298, 14}>)
1.future position animation Optional(<CAAnimation:0x170625780; duration = 0.25>)
2.future position animation Optional(<CABasicAnimation:0x170625820; duration = 0.25; fillMode = backwards; timingFunction = default; keyPath = position; fromValue = NSPoint: {418.94695306710298, 14}>)

开头是 CABasicAnimation,指向 position keyPath,持续时间为 250 毫秒。我把它吹走了(通过 removeAllAnimations())并添加了一个存根 CAAnimation 希望它什么都不做。 1 打印显示这确实发生了。但是我设置了frame 属性之后,又恢复原样了。似乎只是设置帧重置这些动画。

有没有办法在不发生这些动画的情况下设置帧?我希望系统中的其他动画继续工作。我只是希望这个特定的遮罩层不具有动画效果。

最后,我发现唯一有效的方法是执行以下操作:

override func layoutSubviews() {
    super.layoutSubviews()
    ...
    if let futureMask = self.futureShape.mask {
        let nowX = computeNowX()
        CATransaction.setDisableActions(true)
        futureMask.frame = CGRect(x: box.left + nowX, y: box.top, width: box.width - nowX, height: box.height)
        CATransaction.commit()
    }
}

基本上,我只是用 CATransaction 调用来包装框架的设置以禁用操作并强制提交。

已更新

@Hlung 的回答比较正确。 remove的设置后,添加了两个动画(此时打印animationKeys()所示)。我添加了以下扩展方法来捕获模式:

extension CALayer {
    func dontAnimate(_ closure:(CALayer) -> ()) {
        closure(self)
        self.removeAllAnimations()
    }
}

这让我可以编写如下代码:

self.someSubLayer.dontAnimate { layer in layer.frame = whatever }

dontAnimate 的一个更强大的变体可能首先快照当前动画,运行 closure然后 removeAllAnimations() 然后恢复快照的。

只需调用层 removeAllAnimations() 设置框架之后。

class GradientView: UIView {

  private(set) var gradientLayer: CAGradientLayer

  override init(frame: CGRect) {
    gradientLayer = CAGradientLayer()
    super.init(frame: frame)
    layer.insertSublayer(gradientLayer, at: 0)
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("not implemented")
  }

  override func layoutSubviews() {
    super.layoutSubviews()
    gradientLayer.frame = bounds
    gradientLayer.removeAllAnimations() // remove implicit animation from frame change
  }

}