首页 > 解决方案 > CABasicAnimation 不适用于 CAGradientLayer

问题描述

我正在尝试为 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)

代码示例取自此处

标签: iosswiftuibuttoncabasicanimationcagradientlayer

解决方案


动画位置属性工作得很好。

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

在此处输入图像描述

我还合并了 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()

推荐阅读