禁用 CALayer 蒙版帧更改动画

Posted

技术标签:

【中文标题】禁用 CALayer 蒙版帧更改动画【英文标题】:Disable CALayer mask frame change animations 【发布时间】:2017-01-05 23:22:05 【问题描述】:

我有一个 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 属性之后,它又变回了原来的样子。似乎只是设置框架会重置这些动画。

没有这些动画就没有办法设置框架吗?我希望系统中的其他动画继续工作。我只是想让这个特殊的遮罩层不设置动画。

【问题讨论】:

为什么投反对票? 【参考方案1】:

只需调用层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
  


【讨论】:

这是一个更好的答案。【参考方案2】:

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

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() 然后恢复快照的动画。

【讨论】:

我发现这不是一个好的解决方案。 CATransaction.setDisableActions(true) 可以影响全球每一层。使用它,我会在整个应用程序中遇到奇怪的动画和布局,例如视图控制器推送没有动画,系统标签栏不知何故不再尊重底部安全区域。即使我将 setDisableActions 恢复到以前的值,它仍然没有帮助。对我有用的只是在设置layer.frame 后调用layer.removeAllAnimations()。我将添加一个新答案。

以上是关于禁用 CALayer 蒙版帧更改动画的主要内容,如果未能解决你的问题,请参考以下文章

如何为 CALayer 的角半径设置动画

图像更改 CALayer 核心动画

选择性地覆盖 CALayer 隐式动画

如何在更改其边界的动画期间强制重绘 CALayer

动画边界更改时具有 CALayer 有线效果的自定义视图

动画边界更改时具有 CALayer 有线效果的自定义视图