在动画的UIView上动画图层

5
我在一个视图中有一个图层动画方面的问题。我已经通过Google搜索了该问题,但是只找到使用CATransaction的答案,假设我知道其边界的fromValuetoValue。我有一个在点击时调整大小的tableHeader的视图,在UIView.animate()块中它作为一个普通的UIView进行动画处理,效果正常。这个视图有一个CAGradientLayer作为子层,以给它一个渐变的背景颜色。当视图动画其高度时,该层不会随之动画。该层会立即更改其边界,当动画开始时。
为了确保该层在总体上得到正确的大小(在初始化/加载/屏幕旋转等过程中),我被告知要这样做:
override func layoutSubviews() {
    super.layoutSubviews()
    gradientLayer.frame = backgroundView.bounds
}

它每次都可以得到正确的尺寸,但从未进行过动画处理。

为了执行我的视图动画,我这样做:

self.someLabelHeightConstraint.constant = someHeight
UIView.animate(withDuration: 0.3, animations: { [weak self] in                
    self?.layoutIfNeeded()
})

这段代码看起来完美无瑕,但我认为layoutIfNeeded()在某个时刻会调用layoutSubviews,这会破坏我添加到块中的任何CALayer动画。

正如你所见,我只改变了我的视图中设置的约束的常量,因此我实际上不知道完成动画后实际标题视图的大小将是多少。我可能可以进行一些数学计算来找出它的大小,但这似乎是不必要的。
难道没有更好的方法吗?
2个回答

9

有一些笨拙的方法可以在动画期间更新子层的frame,但最优雅的解决方案是让UIKit为您处理。创建一个子类UIView,其layerClassCAGradientLayer。当视图被创建时,CAGradientLayer将会为您创建。当您动画视图的框架时,渐变层会为您平稳地进行动画。

@IBDesignable
public class GradientView: UIView {

    @IBInspectable public var startColor: UIColor = .white { didSet { updateColors() } }
    @IBInspectable public var endColor:   UIColor = .black { didSet { updateColors() } }

    override open class var layerClass: AnyClass { return CAGradientLayer.self }

    override public init(frame: CGRect = .zero) {
        super.init(frame: frame)
        config()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        config()
    }

    private func config() {
        updateColors()
    }

    private func updateColors() {
        let gradientLayer = layer as! CAGradientLayer

        gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
    }

}

注意,我已将此设置为@IBDesignable,因此您可以将其放入框架目标中,然后在IB中使用它,但这取决于您。这不是必需的。主要问题是覆盖layerClass,以便UIKit负责在动画视图时动画层。

enter image description here


这个方法非常有效,谢谢!但我不太确定为什么会这样。发生了什么?UIView中的原始CALayer是否具有内置逻辑来实现这一点,而您基本上创建了一个UIView,其中原始CALayer是CAGradientLayer,从而赋予它所有这些魔力?起初我也是这么认为的,但当我尝试设置self.layer = gradientLayer时,我被告知self.layer是只读的。您的解决方案真是太棒了。 - Sti
1
这太聪明了!谢谢。 - Andrei Marcu
1
@iVentis 不,这对于CAShapeLayer不起作用,因为您需要更新path。选项包括CABasicAnimationCADisplayLink驱动的动画。最简单的选择是在draw(_:)方法中呈现边框(并使用.redrawcontentMode)...描边在动画中间不完美,但如果您的动画时间相当短,没有人会注意到。但是,所有这些都超出了这个问题的范围,我建议您发布自己的问题... - Rob
2
@Rob 谢谢你的快速回答!我通过使用相同的动画选项对图层进行动画处理来解决了这个问题。https://medium.com/@maxcampolo/animating-uiview-sublayers-fa549e82cbbd - iVentis
这个解决方案之所以有效,是因为您更改了UIView的后备层。而UIView(作为后备层的代理)在UIKit动画期间为图层提供“action”。对于后备层的子层,您可以使用viewBackingLayer.action(forKey: "backgroundColor") as? CABasicAnimation来获取此类操作。然后将此动画用作动画持续时间和时间函数的参考。然后为子层添加自定义动画。 - Honghao Z
显示剩余2条评论

1
你需要移除 CALayer 的动作。 添加以下代码:
gradientLayer.actions = ["position": NSNull(),"frame":NSNull(),"bounds":NSNull()]

这会解决什么问题?它会使我的图层自动与UIView.animate代码配合工作,还是使我的图层自己的CATransaction动画与UIView.animate一起工作? - Sti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接