如何使循环加载动画的颜色在加载时从红色变为绿色
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.
您缺少的部分是计算需要根据当前值进行插值的 from
和 to
颜色。
假设您的进度视图将在某个时候更新界面以具有
var minimumValue: CGFloat = 0.0
var maximumValue: CGFloat = 100.0
var currentValue: CGFloat = 30.0 // 30%
然后使用这些值您可以计算当前进度,一个介于 0
和 1
之间的值应该定义颜色插值比例。计算它的基本数学应该是:
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()
}
}
请随意使用和修改它。
我已经实现了一个循环加载动画,就像这个视频中那样:
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
到
您缺少的部分是计算需要根据当前值进行插值的 from
和 to
颜色。
假设您的进度视图将在某个时候更新界面以具有
var minimumValue: CGFloat = 0.0
var maximumValue: CGFloat = 100.0
var currentValue: CGFloat = 30.0 // 30%
然后使用这些值您可以计算当前进度,一个介于 0
和 1
之间的值应该定义颜色插值比例。计算它的基本数学应该是:
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()
}
}
请随意使用和修改它。