禁用 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
}
}
我有一个 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
}
}