如何使用 CATransaction 为 CALayer 的出现设置动画?
How to animate the appearing of a CALayer using CATransaction?
我想制作一个 CALAyer
的动画,让它看起来像是凭空出现的。我想我可以先将图层的变换设置为“缩放到 x: 0, y: 0”。然后做一个动画把变换设置为“scale to x: 1, y: 1”,也就是恒等式。我认为这会使图层看起来从无到有。然而,当我尝试实现这一点时,图层立即出现而没有动画。
MCVE:
class MyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
backgroundColor = .clear
clipsToBounds = false
setupLayers()
}
var squareLayer: CAShapeLayer!
private func setupLayers() {
layer.sublayers?.forEach { [=10=].removeFromSuperlayer() }
squareLayer = CAShapeLayer()
layer.addSublayer(squareLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
drawLayers()
}
func drawLayers() {
squareLayer.fillColor = UIColor.red.cgColor
squareLayer.path = UIBezierPath(rect: bounds).cgPath
squareLayer.frame = bounds
}
}
然后在我的 VC:
@IBAction func click() {
myView = MyView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view.addSubview(myView)
myView.drawLayers()
myView.squareLayer.setAffineTransform(.init(scaleX: 0, y: 0))
CATransaction.begin()
CATransaction.setAnimationDuration(1)
self.myView.squareLayer.setAffineTransform(.init(scaleX: 1, y: 1))
CATransaction.commit()
}
我认为这是因为第一个 setAffineTransform
调用也是动画的(我不知道为什么会产生影响,但我怀疑这可能会产生影响),所以我尝试移动第二次调用完成块:
@IBAction func click() {
myView = MyView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view.addSubview(myView)
myView.drawLayers()
CATransaction.begin()
CATransaction.setCompletionBlock {
CATransaction.begin()
CATransaction.setAnimationDuration(1)
self.myView.squareLayer.setAffineTransform(.init(scaleX: 1, y: 1))
CATransaction.commit()
}
myView.squareLayer.setAffineTransform(.init(scaleX: 0, y: 0))
CATransaction.commit()
}
但是,输出是一样的。另一方面,如果我在第一个 setAffineTransform
调用后稍等片刻,那么它会起作用:
@IBAction func click() {
myView = MyView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view.addSubview(myView)
myView.drawLayers()
myView.squareLayer.setAffineTransform(.init(scaleX: 0, y: 0))
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
CATransaction.begin()
CATransaction.setAnimationDuration(1)
self.myView.squareLayer.setAffineTransform(.init(scaleX: 1, y: 1))
CATransaction.commit()
}
}
我不认为这是我应该如何解决这个问题。据推测,在那段时间里,调用了一些生命周期方法(我不知道是哪个),而我应该做的是在该生命周期方法之后进行第二次 setAffineTransform
调用......(?)
我知道我可以为此使用 CABasicAnimation
,但我不喜欢我需要使用委托来检测动画结束的方式。这让很多事情对我来说非常麻烦。由于延迟 0.1 秒有效,因此 CATransaction
s 这不是不可能的,对吧?
如何使用动画使 CALayer
出现?
On the other hand, if I wait for a bit
没错。层必须是渲染树的一部分才能进行动画处理,因此您不能添加层并将其作为同一事务的一部分进行动画处理。因此,您的异步调用在附加动画之前等待当前事务提交和下一个事务开始,实际上是一个完全正确的解决方案。
另一种可能是在添加图层后调用CATransaction.flush()
;这有效地提前提交了当前事务。
我想制作一个 CALAyer
的动画,让它看起来像是凭空出现的。我想我可以先将图层的变换设置为“缩放到 x: 0, y: 0”。然后做一个动画把变换设置为“scale to x: 1, y: 1”,也就是恒等式。我认为这会使图层看起来从无到有。然而,当我尝试实现这一点时,图层立即出现而没有动画。
MCVE:
class MyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
backgroundColor = .clear
clipsToBounds = false
setupLayers()
}
var squareLayer: CAShapeLayer!
private func setupLayers() {
layer.sublayers?.forEach { [=10=].removeFromSuperlayer() }
squareLayer = CAShapeLayer()
layer.addSublayer(squareLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
drawLayers()
}
func drawLayers() {
squareLayer.fillColor = UIColor.red.cgColor
squareLayer.path = UIBezierPath(rect: bounds).cgPath
squareLayer.frame = bounds
}
}
然后在我的 VC:
@IBAction func click() {
myView = MyView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view.addSubview(myView)
myView.drawLayers()
myView.squareLayer.setAffineTransform(.init(scaleX: 0, y: 0))
CATransaction.begin()
CATransaction.setAnimationDuration(1)
self.myView.squareLayer.setAffineTransform(.init(scaleX: 1, y: 1))
CATransaction.commit()
}
我认为这是因为第一个 setAffineTransform
调用也是动画的(我不知道为什么会产生影响,但我怀疑这可能会产生影响),所以我尝试移动第二次调用完成块:
@IBAction func click() {
myView = MyView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view.addSubview(myView)
myView.drawLayers()
CATransaction.begin()
CATransaction.setCompletionBlock {
CATransaction.begin()
CATransaction.setAnimationDuration(1)
self.myView.squareLayer.setAffineTransform(.init(scaleX: 1, y: 1))
CATransaction.commit()
}
myView.squareLayer.setAffineTransform(.init(scaleX: 0, y: 0))
CATransaction.commit()
}
但是,输出是一样的。另一方面,如果我在第一个 setAffineTransform
调用后稍等片刻,那么它会起作用:
@IBAction func click() {
myView = MyView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view.addSubview(myView)
myView.drawLayers()
myView.squareLayer.setAffineTransform(.init(scaleX: 0, y: 0))
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
CATransaction.begin()
CATransaction.setAnimationDuration(1)
self.myView.squareLayer.setAffineTransform(.init(scaleX: 1, y: 1))
CATransaction.commit()
}
}
我不认为这是我应该如何解决这个问题。据推测,在那段时间里,调用了一些生命周期方法(我不知道是哪个),而我应该做的是在该生命周期方法之后进行第二次 setAffineTransform
调用......(?)
我知道我可以为此使用 CABasicAnimation
,但我不喜欢我需要使用委托来检测动画结束的方式。这让很多事情对我来说非常麻烦。由于延迟 0.1 秒有效,因此 CATransaction
s 这不是不可能的,对吧?
如何使用动画使 CALayer
出现?
On the other hand, if I wait for a bit
没错。层必须是渲染树的一部分才能进行动画处理,因此您不能添加层并将其作为同一事务的一部分进行动画处理。因此,您的异步调用在附加动画之前等待当前事务提交和下一个事务开始,实际上是一个完全正确的解决方案。
另一种可能是在添加图层后调用CATransaction.flush()
;这有效地提前提交了当前事务。