如何使循环加载动画的颜色在加载时从红色变为绿色

How to make circular loading animation color go from red to green as it loads

我已经实现了一个循环加载动画,就像这个视频中那样:

https://www.youtube.com/watch?v=O3ltwjDJaMk

这是我的代码:

class ViewController: UIViewController {

let shapeLayer = CAShapeLayer()

override func viewDidLoad() {
        super.viewDidLoad()
        
        let center = view.center
        let trackLayer = CAShapeLayer()
        
        let circularPath = UIBezierPath(arcCenter: center, radius: 100, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
        trackLayer.path = circularPath.cgPath
        trackLayer.strokeColor = UIColor.lightGray.cgColor
        trackLayer.lineWidth = 10
        trackLayer.fillColor = UIColor.clear.cgColor
        trackLayer.lineCap = kCALineCapRound
        view.layer.addSublayer(trackLayer)
        
        shapeLayer.path = circularPath.cgPath
        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.lineWidth = 10
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineCap = kCALineCapRound
        shapeLayer.strokeEnd = 0
     
        view.layer.addSublayer(shapeLayer)
        
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap)))
    }
    
    @objc private func handleTap() {
 
        let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
        
        basicAnimation.toValue = 1
        basicAnimation.duration = 2
        
        basicAnimation.fillMode = kCAFillModeForwards
        basicAnimation.isRemovedOnCompletion = false
        
        shapeLayer.add(basicAnimation, forKey: "urSoBasic")
    }
}

当动画发生时,我希望圆圈的颜色从红色变为绿色,如果圆圈完全完成,它应该是绿色,如果不是,那么红绿之间的一些颜色(取决于用户的变量-ID)。任何人都知道如何做到这一点?感谢您的帮助:)

您也可以使用 CAAnimation。所以你可以遵循 link.

您缺少的部分是计算需要根据当前值进行插值的 fromto 颜色。

假设您的进度视图将在某个时候更新界面以具有

var minimumValue: CGFloat = 0.0
var maximumValue: CGFloat = 100.0
var currentValue: CGFloat = 30.0 // 30%

然后使用这些值您可以计算当前进度,一个介于 01 之间的值应该定义颜色插值比例。计算它的基本数学应该是:

let progress = (currentValue-maximumValue)/(minimumValue-maximumValue) // TODO: handle division by zero. Handle current value out of bounds.

随着进步,您现在可以使用

插入任何数值
func interpolate(_ values: (from: CGFloat, to: CGFloat), scale: CGFloat) -> CGFloat {
    return values.from + (values.to - values.from)*scale
}

但颜色不是数值。在 CAAnimation 的情况下,您应该将其分解为 4 个数值作为 RGBA 以保持一致性(至少我相信 CAAnimation 使用 RGBA space 中的颜色插值)。就像一张纸条;在许多情况下,当您在 HSV space 而不是 RGB 中进行插值时,它看起来更好。

所以插值颜色应该是这样的:

func rgba(_ color: UIColor) -> (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) {
    var r: CGFloat = 0.0
    var g: CGFloat = 0.0
    var b: CGFloat = 0.0
    var a: CGFloat = 0.0
    color.getRed(&r, green: &g, blue: &b, alpha: &a)
    return (r, g, b, a)
}
func interpolate(_ values: (from: CGFloat, to: CGFloat), scale: CGFloat) -> CGFloat {
    return values.from + (values.to - values.from)*scale
}
func interpolate(_ values: (from: UIColor, to: UIColor), scale: CGFloat) -> UIColor {
    let startColorComponents = rgba(values.from)
    let endColorComponents = rgba(values.to)
    return .init(red: interpolate((startColorComponents.r, endColorComponents.r), scale: scale),
                 green: interpolate((startColorComponents.g, endColorComponents.g), scale: scale),
                 blue: interpolate((startColorComponents.b, endColorComponents.b), scale: scale),
                 alpha: interpolate((startColorComponents.a, endColorComponents.a), scale: scale))
}

这些应该是您制作动画所需的所有组件,我希望它们足以让您走上正轨。我个人喜欢有更多的控制权,而且我喜欢避免使用 CAAnimation 甚至形状图层等工具。所以这就是我完成你的任务的方式:

class ViewController: UIViewController {

    @IBOutlet private var progressView: ProgressView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
     
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTap)))
    }
    
    @objc private func onTap() {
        guard let progressView = progressView else { return }
        progressView.animateValue(to: .random(in: progressView.minimumValue...progressView.maximumValue), duration: 0.3)
    }


}

@IBDesignable class ProgressView: UIView {
    
    @IBInspectable var minimumValue: CGFloat = 0.0 { didSet { setNeedsDisplay() } }
    @IBInspectable var maximumValue: CGFloat = 0.0 { didSet { setNeedsDisplay() } }
    @IBInspectable var value: CGFloat = 0.0 { didSet { setNeedsDisplay() } }
    @IBInspectable var colorAtZeroProgress: UIColor = .black { didSet { setNeedsDisplay() } }
    @IBInspectable var colorAtFullProgress: UIColor = .black { didSet { setNeedsDisplay() } }
    @IBInspectable var lineWidth: CGFloat = 10.0 { didSet { setNeedsDisplay() } }
    
    private var currentTimer: Timer?
    func animateValue(to: CGFloat, duration: TimeInterval) {
        currentTimer?.invalidate()
        let startTime = Date()
        let startValue = self.value
        currentTimer = Timer.scheduledTimer(withTimeInterval: 1.0/60.0, repeats: true, block: { timer in
            let progress = CGFloat(max(0.0, min(Date().timeIntervalSince(startTime)/duration, 1.0)))
            if progress >= 1.0 {
                // End of animation
                timer.invalidate()
            }
            self.value = startValue + (to - startValue)*progress
        })
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
         
        let progress = max(0.0, min((value-minimumValue)/(maximumValue-minimumValue), 1.0))
        
        let center = CGPoint(x: bounds.midX, y: bounds.midY)
        let radius = min(bounds.width, bounds.height)*0.5 - lineWidth*0.5
        let startAngle: CGFloat = -.pi*2.0
        let endAngle: CGFloat = startAngle + .pi*2.0*progress
        let circularPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
        
        
        
        let interpolatedColor: UIColor = {
            func rgba(_ color: UIColor) -> (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) {
                var r: CGFloat = 0.0
                var g: CGFloat = 0.0
                var b: CGFloat = 0.0
                var a: CGFloat = 0.0
                color.getRed(&r, green: &g, blue: &b, alpha: &a)
                return (r, g, b, a)
            }
            func interpolate(_ values: (from: CGFloat, to: CGFloat), scale: CGFloat) -> CGFloat {
                return values.from + (values.to - values.from)*scale
            }
            func interpolate(_ values: (from: UIColor, to: UIColor), scale: CGFloat) -> UIColor {
                let startColorComponents = rgba(values.from)
                let endColorComponents = rgba(values.to)
                return .init(red: interpolate((startColorComponents.r, endColorComponents.r), scale: scale),
                             green: interpolate((startColorComponents.g, endColorComponents.g), scale: scale),
                             blue: interpolate((startColorComponents.b, endColorComponents.b), scale: scale),
                             alpha: interpolate((startColorComponents.a, endColorComponents.a), scale: scale))
            }
            
            return interpolate((self.colorAtZeroProgress, self.colorAtFullProgress), scale: progress)
        }()
        
        interpolatedColor.setStroke()
        circularPath.lineCapStyle = .round
        circularPath.lineWidth = lineWidth
        circularPath.stroke()
    }
    
    
}

请随意使用和修改它。