渐变动画无法正常工作

问题描述 投票:0回答:1

有一个问题是我的渐变动画无法正常工作。如果你向gradientColors数组中添加更多的颜色,它或多或少会起作用,但会有闪烁的效果。

correct animation behavior

尝试通过CATransaction解决问题,但没有解决问题。当我向数组添加超过 2 种颜色时,动画似乎可以工作,但有闪烁效果

there are more than two colors in the colors array

there are 2 colors in the array

也许你有解决问题的办法?我将非常感谢您的帮助:)

github项目

class ViewController: UIViewController {
private enum Color {
    static var gradientColors: [UIColor] = [
        UIColor(red: 255, green: 255, blue: 255, alpha: 1),
        UIColor(red: 255, green: 255, blue: 255, alpha: 0)
    ]
}

@IBOutlet weak var gradientView: UIView! {
    didSet {
        gradientView.layer.cornerRadius = 16
    }
}

private var timer: Timer?

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

func animateBorderGradietion() {
       let shape1 = CAShapeLayer()
       shape1.path = UIBezierPath(
           roundedRect: gradientView.bounds.insetBy(dx: 1.0, dy: 1.0),
           cornerRadius: gradientView.layer.cornerRadius
       ).cgPath
       
       shape1.lineWidth = 1.0
       shape1.strokeColor = UIColor.white.cgColor
       shape1.fillColor = UIColor.clear.cgColor
       
       let gradient1 = CAGradientLayer()
       gradient1.frame = gradientView.bounds
       gradient1.type = .conic
       gradient1.colors = Color.gradientColors.map { $0.cgColor }
       gradient1.locations = calculateGradientLocation()
       gradient1.startPoint = CGPoint(x: 0.5, y: 0.5)
       gradient1.endPoint = CGPoint(x: 1, y: 1)
       gradient1.mask = shape1
       gradientView.layer.addSublayer(gradient1)
       
       let shape2 = CAShapeLayer()
       shape2.path = UIBezierPath(
           roundedRect: gradientView.bounds.insetBy(dx: 1.0, dy: 1.0),
           cornerRadius: gradientView.layer.cornerRadius
       ).cgPath
       
       shape2.lineWidth = 1.0
       shape2.strokeColor = UIColor.white.cgColor
       shape2.fillColor = UIColor.clear.cgColor
       
       let gradient2 = CAGradientLayer()
       gradient2.frame = gradientView.bounds
       gradient2.type = .conic
       gradient2.colors = Color.gradientColors.map { $0.cgColor }
       gradient2.locations = calculateGradientLocation()
       gradient2.startPoint = CGPoint(x: 1, y: 1)
       gradient2.endPoint = CGPoint(x: 0.5, y: 0.5)
       gradient2.mask = shape2
       gradientView.layer.addSublayer(gradient2)
       
       self.timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { _ in
           CATransaction.begin()
           CATransaction.setAnimationDuration(0.2)
           
           CATransaction.setCompletionBlock {
               gradient1.removeAnimation(forKey: "myAnimation")
               gradient2.removeAnimation(forKey: "myAnimation")
               
               let previous = Color.gradientColors.map { $0.cgColor }
               let last = Color.gradientColors.removeLast()
               Color.gradientColors.insert(last, at: 0)
               let lastColors = Color.gradientColors.map { $0.cgColor }
               
               let colorsAnimation = CABasicAnimation(keyPath: "colors")
               colorsAnimation.fromValue = previous
               colorsAnimation.toValue = lastColors
               colorsAnimation.repeatCount = 1
               colorsAnimation.duration = 0.2
               colorsAnimation.isRemovedOnCompletion = false
               colorsAnimation.fillMode = .both
               
               gradient1.colors = lastColors
               gradient2.colors = lastColors
               gradient1.add(colorsAnimation, forKey: "myAnimation")
               gradient2.add(colorsAnimation, forKey: "myAnimation")
           }
           
           CATransaction.commit()
       }
    }


private func calculateGradientLocation() -> [NSNumber] {
    return Array(stride(from: 0, to: Color.gradientColors.count, by: 1))
        .map { NSNumber(value: Double($0) / Double(Color.gradientColors.count)) }
}

}

ios swift uikit cabasicanimation cagradientlayer
1个回答
0
投票

另一种方法是旋转

CAGradientLayer
,而不是尝试对渐变颜色和位置进行动画处理。

所以,如果我们从一个简单的视图开始:

我们可以添加一个

CAGradientLayer
作为子图层 - 在这里,它只有一个 1/2 alpha 颜色,因此我们可以轻松地看到框架:

因为我们将旋转该层,所以我们将该层的框架设置为大于视图,这样它将完全覆盖它:

接下来,我们将渐变层的颜色设置为

clear, white, clear

最后,对视图应用形状图层蒙版:

这是它在黑色背景上的外观:

这里有一些动画版本(太大而无法嵌入此处):https://imgur.com/a/JXgyT7b

这是一些示例代码:

// couple helpers for CGRect
extension CGRect {
    public var center: CGPoint { return CGPoint(x: midX, y: midY) }
    public var diagonalExtent: CGFloat { return hypot(width, height) }
}

// example view
class AnimatedGradientBorderedView: UIView {

    public var lineWidth: CGFloat = 1.0 { didSet { mskLayer.lineWidth = lineWidth } }
    public var cornerRadius: CGFloat = 16.0 { didSet { setNeedsLayout() } }
    
    private let gradLayer = CAGradientLayer()
    private let mskLayer = CAShapeLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder:aDecoder)
        commonInit()
    }
    
    func commonInit() {

        gradLayer.colors = [UIColor.clear.cgColor, UIColor.white.cgColor, UIColor.clear.cgColor]
        layer.addSublayer(gradLayer)

        mskLayer.fillColor = UIColor.clear.cgColor
        // any opaque color
        mskLayer.strokeColor = UIColor.black.cgColor
        mskLayer.lineWidth = lineWidth
        
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()

        // we need the gradient layer frame to cover the entire view
        //  when it rotates, so we make it a square with width/height
        //  equal to the diagonal dimension of self (plus just a little "padding")
        let diag: CGFloat = bounds.diagonalExtent + 2.0
        gradLayer.frame = bounds.insetBy(dx: -(diag - bounds.width) * 0.5, dy: -(diag - bounds.height) * 0.5)
        
        // rounded-corners mask path, inset by lineWidth so it is completely inside of self
        mskLayer.path = UIBezierPath(roundedRect: bounds.insetBy(dx: lineWidth, dy: lineWidth), cornerRadius: cornerRadius).cgPath
        layer.mask = mskLayer

        doAnim()
    }
    
    func doAnim() {
        
        // rotate the mask layer
        let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
        rotateAnimation.fromValue = 0.0
        rotateAnimation.toValue = CGFloat(Double.pi * 2)
        rotateAnimation.isRemovedOnCompletion = false
        // adjust rotation speed as desired
        rotateAnimation.duration = 8.0
        rotateAnimation.repeatCount=Float.infinity
        gradLayer.add(rotateAnimation, forKey: nil)
        
    }
    
}

// simple example view controller
class MyViewController: UIViewController {
    
    let testView = AnimatedGradientBorderedView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        testView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(testView)
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            
            testView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 34.0),
            testView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -34.0),
            testView.heightAnchor.constraint(equalToConstant: 492.0),
            testView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            
        ])
        
        view.backgroundColor = .black
        testView.backgroundColor = .clear
        
    }
    
}
© www.soinside.com 2019 - 2024. All rights reserved.