【问题标题】:CABasicAnimation not working on CAGradientLayerCABasicAnimation 不适用于 CAGradientLayer
【发布时间】:2021-08-29 12:31:13
【问题描述】:

我正在尝试为 UIButton 创建 Shimmer 效果,因此我将其子类化并尝试启动动画,但没有任何反应。我可以看到按钮上的渐变,但它没有动画。我做错了什么?

看起来是这样的:

这是我的代码:

class ShimmerButton: UIButton {
    
    private var gradientColorOne : CGColor = UIColor(white: 0.85, alpha: 0.0).cgColor
    private var gradientColorTwo : CGColor = UIColor(white: 0.95, alpha: 0.5).cgColor
    
    func addGradientLayer() -> CAGradientLayer {
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = self.bounds
        gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
        gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
        gradientLayer.colors = [self.gradientColorOne, self.gradientColorTwo, self.gradientColorOne]
        gradientLayer.locations = [0.0, 0.5, 1.0]
        let top = self.layer.sublayers?.last
        self.layer.insertSublayer(gradientLayer, above: top)
        return gradientLayer
    }
    
    func addAnimation(duration: Double, repeatCount: Float) -> CABasicAnimation {
        let animation = CABasicAnimation(keyPath: "locations")
        animation.fromValue = [-1.0, -0.5, 0.0]
        animation.toValue = [1.0, 1.5, 2.0]
        animation.repeatCount = repeatCount
        animation.duration = duration
        return animation
    }
    
    func startAnimating(duration: Double, repeatCount: Float) {
        let gradientLayer = self.addGradientLayer()
        let animation = self.addAnimation(duration: duration, repeatCount: repeatCount)
        gradientLayer.add(animation, forKey: "anim")
    }
}

开始动画:

self.button.startAnimating(duration: 0.9, repeatCount: .infinity)

代码示例取自here

【问题讨论】:

  • 虽然 Apple 的文档确实声明了 The gradient stops are specified as values between 0 and 1,但我已经看到许多使用超出该范围的值的示例,所以我认为这不是问题所在。我尝试了您的代码-与您发布的完全一样-它可以正常工作。问题很可能是您对该按钮的外观所做的任何其他操作,此处未在您的代码中显示。
  • 我没有对它做任何事情,只是设置一个 backgroundImage。
  • 这是我使用您未修改的代码得到的结果,图像设置为按钮的背景:i.stack.imgur.com/UYJwH.gif
  • 这很奇怪,因为在我的情况下,渐变图层蒙版不会动画。
  • 请注意,当您编写代码时,一旦添加了您的 gradientLayer,您就无法删除它。我在答案中更新了 ShimmerButton 类,以将 gradientLayer 保存到实例 var 中,以便您可以启动/停止动画并根据需要删除渐变层。

标签: ios swift uibutton cabasicanimation cagradientlayer


【解决方案1】:

为位置属性设置动画效果很好。

我编写了一个创建简单渐变动画的游乐场。它看起来像这样:

我还加入了 OPs ShimmerButton(进行了一些更改,以便您可以启动/停止微光动画。微光动画如下所示:

这是那个游乐场的代码:

 //: A UIKit based Playground for presenting user interface

import UIKit
import PlaygroundSupport

class ShimmerButton: UIButton {
    public var animating = false

    private var gradientLayer: CAGradientLayer?
    private var gradientColorOne : CGColor = UIColor(white: 0.85, alpha: 0.0).cgColor
    private var gradientColorTwo : CGColor = UIColor(white: 0.95, alpha: 0.5).cgColor

    private func addGradientLayer() -> CAGradientLayer? {
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = self.bounds
        gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
        gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
        gradientLayer.colors = [self.gradientColorOne, self.gradientColorTwo, self.gradientColorOne]
        gradientLayer.locations = [-1.0, -0.5, 0.0]
        let top = self.layer.sublayers?.last
        self.layer.insertSublayer(gradientLayer, above: top)
        return gradientLayer
    }

    public func addAnimation(duration: Double, repeatCount: Float) -> CABasicAnimation {
        let animation = CABasicAnimation(keyPath: "locations")
        animation.fromValue = [-1.0, -0.5, 0.0]
        animation.toValue = [1.0, 1.5, 2.0]
        animation.repeatCount = repeatCount
        animation.duration = duration
        animation.delegate = self
        return animation
    }

    public func stopAnimating() {
        guard let  gradientLayer = gradientLayer  else { return }
        gradientLayer.removeFromSuperlayer()
        gradientLayer.removeAllAnimations()
        self.gradientLayer = nil
        animating = false
    }

    public func startAnimating(duration: Double, repeatCount: Float) {
        if !animating {
            animating = true
            gradientLayer = self.addGradientLayer()
            let animation = self.addAnimation(duration: duration, repeatCount: repeatCount)
            gradientLayer?.add(animation, forKey: "anim")
        }
    }
}

extension ShimmerButton: CAAnimationDelegate {
    func animationDidStop(_ anim: CAAnimation, finished: Bool) {
        animating = false
        if finished {
            gradientLayer?.removeFromSuperlayer()
            gradientLayer = nil;
        }
    }
}

class GradientView: UIView {

    var animationIsLeft: Bool = false
    var animating = false

    static override var layerClass: AnyClass {
        return CAGradientLayer.self
    }
    public func animateGradient() {
        animating = true
        animateGradientStep()
    }

    public func animateGradientStep() {
        guard animating,
              let layer = layer as? CAGradientLayer else { return }
        let animation = CABasicAnimation(keyPath:"locations")
        animation.duration = 0.5
        animationIsLeft = !animationIsLeft
        animation.fromValue = animationIsLeft ? [0, 0.1, 1] : [0, 0.9, 1.0]
        let toValue = !animationIsLeft ? [0, 0.1, 1] : [0, 0.9, 1.0]
        animation.toValue = toValue
        animation.delegate = self
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.02) {
            layer.locations = toValue.map { NSNumber(value: $0) }
        }
        layer.add(animation, forKey: nil)
    }

    func doInitSetup() {
        guard let layer = layer as? CAGradientLayer else { return }
        layer.startPoint = CGPoint(x: 0.0, y: 1.0)
        layer.endPoint = CGPoint(x: 1.0, y: 1.0)
        layer.colors = [UIColor.blue.cgColor, UIColor.red.cgColor, UIColor.blue.cgColor]
        layer.locations = [0, 0.1, 1]
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        doInitSetup()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        doInitSetup()
    }
}

extension GradientView: CAAnimationDelegate {
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        animateGradientStep()
    }
}

class MyViewController : UIViewController {
    var gradientView: GradientView?
    var button: ShimmerButton?

    override func loadView() {
        let view = UIView()
        view.backgroundColor = .white
        view.layer.borderWidth = 1
        self.view = view

        gradientView = GradientView()
        if  let gradientView = gradientView {
            gradientView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(gradientView)
            gradientView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true
            gradientView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100).isActive = true
            gradientView.heightAnchor.constraint(equalToConstant: 200).isActive = true
            gradientView.widthAnchor.constraint(equalToConstant: 200).isActive = true
            gradientView.layer.borderWidth = 1
        }

        button = ShimmerButton()
        guard let button = button else { return }
        button.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(button)
        button.setTitle("Button", for: .normal)
        button.backgroundColor = UIColor.lightGray
        button.layer.borderWidth = 1
        button.layer.cornerRadius = 10
        button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20).isActive = true
        button.widthAnchor.constraint(equalToConstant: 120).isActive = true
        button.heightAnchor.constraint(equalToConstant: 30).isActive = true
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
    }

    @IBAction func buttonTapped(_ sender: UIButton) {
        if gradientView?.animating == true {
            gradientView?.animating = false
        } else {
            gradientView?.animateGradient()
        }
        guard let button = button else { return }
        if button.animating == true {
            button.stopAnimating()
        } else {
            button.startAnimating(duration: 1.0, repeatCount: Float.greatestFiniteMagnitude)
        }
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    }
}

PlaygroundPage.current.liveView = MyViewController()

【讨论】:

  • 那么为什么投反对票?我的回答是尝试确认动画渐变视图的位置属性按预期工作。 (我后来将 OP 的 ShimmerButton 添加到了操场上。)
猜你喜欢
  • 2014-07-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-27
  • 1970-01-01
  • 2020-09-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多