前言:
进了新公司,发现代码全都是第三方拼接的。虽然实现了功能,但定制很麻烦。以前单色的进度条,实现
简单,现在需要做到颜色渐变,于是就自己写了个控件。(实在不喜欢用第三方,除非必要的)。
实现原理:
其实原理很简单,只是在单色的基础上,把圆环分成多个小段,然后逐个分配颜色。
拓展实现:
控件添加了文字进度的显示;
控件还增加了从无到有的一个动画效果,其实也就是不断绘图的过程。
代码实现:swift
/**********************************/
/********* 渐变环形进度条 **********/
/**********************************/
class XJJGCView: UIView {
/**
***颜色数组
*个数为1时,单色
*个数为2时,双色渐变
*个数为3时,三色渐变,两个双色渐变
*以此类推
*/
var colors: [UIColor]?
//小数:0.2 -- 真实百分比
var progress: CGFloat? {
didSet {
guard let pro = progress else {return}
self.drawWithProgress(pro)
}
}
var lineWidth: CGFloat = 10//线宽度
var backColor: UIColor?//背景圆环颜色
var startAngle: CGFloat = 0//开始角度,默认:0,可选值:0 ~ pi_2
var angleCount: Int = 100//圆弧个数,整个圆划分为多少份
//文字属性
var textFont: UIFont!
var textColor: UIColor!
var isAnimated: Bool = true//是否需要动画,默认:true
private let pi_2 = CGFloat(Double.pi * 2)
private let per_progress: CGFloat = 0.0203//每一帧的变化
//小数:0.2 -- 动画百分比
private var an_progress: CGFloat = 0//累计变化
private var isEndDraw: Bool = false//是否画完圆弧
override init(frame: CGRect) {
super.init(frame: frame)
self.initUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.setSubviewsLayout()
}
private var playLink: CADisplayLink!
private func initUI() {
self.backgroundColor = UIColor.clear
self.textFont = UIFont.boldSystemFont(ofSize: 13)
self.textColor = #colorLiteral(red: 0.2549019754, green: 0.2745098174, blue: 0.3019607961, alpha: 1)
}
private func setSubviewsLayout() {
}
private func drawWithProgress(_ pro: CGFloat) {
guard pro > 0, pro <= 1 else {return}
self.an_progress = 0
if isAnimated {
self.playLink = CADisplayLink(target: self, selector: #selector(timeCounter))
self.playLink.add(to: RunLoop.main, forMode: .defaultRunLoopMode)
if #available(iOS 10.0, *) {
self.playLink.preferredFramesPerSecond = 50//一秒完成的帧数
}else {
self.playLink.frameInterval = 1//调用一次 有多少帧 为1时,每秒60次
}
}else {
self.an_progress = pro
self.isEndDraw = true
self.setNeedsDisplay()
}
}
@objc func timeCounter(_ link: CADisplayLink) {
if self.an_progress >= (self.progress ?? 0) {
self.finished()
}else {
self.an_progress += self.per_progress
self.setNeedsDisplay()
}
}
private func finished() {
self.an_progress = self.progress ?? 0
self.isEndDraw = true
if self.playLink != nil {
self.playLink.invalidate()
self.playLink = nil
}
self.setNeedsDisplay()
}
//找到圆弧上直线经过的点,并返回圆心到这一点所在直线的任意一点,通过scale实现
private func findPoint(_ center: CGPoint,
_ radius: CGFloat,
_ startAngle: CGFloat,
_ moveAngle: CGFloat,
_ scale: CGFloat) -> CGPoint {
let angle: CGFloat = startAngle + moveAngle / 2
let o_x: CGFloat = radius * cos(angle)
let o_y: CGFloat = radius * sin(angle)
let c_x: CGFloat = center.x + o_x * scale
let c_y: CGFloat = center.y + o_y * scale
return CGPoint(x: c_x, y: c_y)
}
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let current = UIGraphicsGetCurrentContext() else {return}
current.clear(rect)
current.setFillColor(UIColor.clear.cgColor)
current.fill(rect)
self.creatGradient(current)
self.creatText(current)
}
//画圆环
private func creatGradient(_ context: CGContext) {
let start: CGFloat = startAngle
let end: CGFloat = start + (progress ?? 0) * pi_2
let center: CGPoint = CGPoint(x: self.bounds.width / 2, y: self.bounds.height / 2)
let r: CGFloat = self.bounds.width / 2 - lineWidth
let anglePer: CGFloat = pi_2 / CGFloat(angleCount)
var currentAngle: CGFloat = start
for i in 0..<angleCount {
autoreleasepool {
let en: CGFloat = start + anglePer * CGFloat(i + 1)
let pro: CGFloat = CGFloat(i) / CGFloat(angleCount)
context.setLineWidth(lineWidth)
if pro < an_progress {//渐变圆弧
let pro_color: CGFloat = (anglePer * CGFloat(i + 1)) / (an_progress * pi_2)
if let color = self.getCurrentColor(pro_color) {
context.setStrokeColor(color.cgColor)
}else {
context.setStrokeColor(#colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1))
}
}else {//固定圆弧
if let color = self.backColor {
context.setStrokeColor(color.cgColor)
}else {
context.setStrokeColor(#colorLiteral(red: 0.501960814, green: 0.501960814, blue: 0.501960814, alpha: 1))
}
}
context.addArc(center: center, radius: r, startAngle: currentAngle, endAngle: en, clockwise: false)
context.drawPath(using: .stroke)
currentAngle = en - 0.01 //为了去掉缝隙,前移一点位移
}
}
if self.isEndDraw {
self.creatPoint(context, start, end, center, r)
self.isEndDraw = false
}
}
//起始点和结束点
private func creatPoint(_ context: CGContext,
_ start: CGFloat,
_ end: CGFloat,
_ center: CGPoint,
_ radius: CGFloat) {
let startP: CGPoint = self.findPoint(center, radius, start, 0, 1)
let endP: CGPoint = self.findPoint(center, radius, end, 0, 1)
let rP: CGFloat = lineWidth / 2 - 1
//start point
self.creatStartPoint(context, startP, rP)
//end point
self.creatEndPoint(context, endP, rP)
}
private func creatStartPoint(_ context: CGContext, _ center: CGPoint, _ radius: CGFloat) {
context.setLineWidth(1)
if let color = colors?.first {
context.setStrokeColor(color.cgColor)
context.setFillColor(color.cgColor)
}else {
context.setStrokeColor(#colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1))
context.setFillColor(#colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1))
}
context.addArc(center: center, radius: radius, startAngle: 0, endAngle: pi_2, clockwise: true)
context.drawPath(using: .fillStroke)
}
private func creatEndPoint(_ context: CGContext, _ center: CGPoint, _ radius: CGFloat) {
context.setLineWidth(1)
if let color = colors?.last {
context.setStrokeColor(color.cgColor)
context.setFillColor(color.cgColor)
}else {
context.setStrokeColor(#colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1))
context.setFillColor(#colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1))
}
context.addArc(center: center, radius: radius + 0.5, startAngle: 0, endAngle: pi_2, clockwise: true)
context.drawPath(using: .fillStroke)
}
//写文字
private func creatText(_ context: CGContext) {
let text = String(format: "%.2f%%", an_progress * 100)
let style = NSMutableParagraphStyle()
style.lineBreakMode = .byWordWrapping
style.alignment = .center
/*之前的写法
let dic: [String : Any] = [
NSFontAttributeName: textFont,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: style,
]
*/
let dic: [NSAttributedStringKey : Any] = [
.font: textFont,
.foregroundColor: textColor,
.paragraphStyle: style,
]
let size = text.size(withAttributes: dic)
let rect = CGRect(x: self.bounds.width / 2 - size.width / 2,
y: self.bounds.height / 2 - size.height / 2,
width: size.width,
height: size.height)
text.draw(in: rect, withAttributes: dic)
}
//获取颜色
private func getCurrentColor(_ current: CGFloat) -> UIColor? {
if let cs = colors, cs.count > 0 {
switch cs.count {
case 1://单色
return cs[0]
case 2://双色
var r: CGFloat = 0;
var g: CGFloat = 0;
var b: CGFloat = 0;
var a: CGFloat = 0;
cs[0].getRed(&r, green: &g, blue: &b, alpha: &a)
var rr: CGFloat = 0;
var gg: CGFloat = 0;
var bb: CGFloat = 0;
var aa: CGFloat = 0;
cs[1].getRed(&rr, green: &gg, blue: &bb, alpha: &aa)
return UIColor(red: current * rr + (1 - current) * r,
green: current * gg + (1 - current) * g,
blue: current * bb + (1 - current) * b,
alpha: current * aa + (1 - current) * a)
case 3://三色
var r: CGFloat = 0;
var g: CGFloat = 0;
var b: CGFloat = 0;
var a: CGFloat = 0;
cs[0].getRed(&r, green: &g, blue: &b, alpha: &a)
var rr: CGFloat = 0;
var gg: CGFloat = 0;
var bb: CGFloat = 0;
var aa: CGFloat = 0;
cs[1].getRed(&rr, green: &gg, blue: &bb, alpha: &aa)
var rrr: CGFloat = 0;
var ggg: CGFloat = 0;
var bbb: CGFloat = 0;
var aaa: CGFloat = 0;
cs[2].getRed(&rrr, green: &ggg, blue: &bbb, alpha: &aaa)
if current > 0.5 {
let cr: CGFloat = (current - 0.5) * 2
return UIColor(red: cr * rrr + (1 - cr) * rr,
green: cr * ggg + (1 - cr) * gg,
blue: cr * bbb + (1 - cr) * bb,
alpha: cr * aaa + (1 - cr) * aa)
}else {
let cr: CGFloat = current * 2;
return UIColor(red: cr * rr + (1 - cr) * r,
green: cr * gg + (1 - cr) * g,
blue: cr * bb + (1 - cr) * b,
alpha: cr * aa + (1 - cr) * a)
}
default://多色
let per: CGFloat = 1 / CGFloat(cs.count - 1)
var startColor: UIColor = UIColor.clear
var endColor: UIColor = UIColor.clear
for i in 0..<cs.count - 1 {
startColor = cs[i]
endColor = cs[i + 1]
if current > (per * CGFloat(i)), current < (per * CGFloat(i + 1)) {
var r: CGFloat = 0;
var g: CGFloat = 0;
var b: CGFloat = 0;
var a: CGFloat = 0;
startColor.getRed(&r, green: &g, blue: &b, alpha: &a)
var rr: CGFloat = 0;
var gg: CGFloat = 0;
var bb: CGFloat = 0;
var aa: CGFloat = 0;
endColor.getRed(&rr, green: &gg, blue: &bb, alpha: &aa)
let cr: CGFloat = (current - per * CGFloat(i)) * CGFloat(cs.count - 1)
return UIColor(red: cr * rr + (1 - cr) * r,
green: cr * gg + (1 - cr) * g,
blue: cr * bb + (1 - cr) * b,
alpha: cr * aa + (1 - cr) * a)
}else if current == (per * CGFloat(i + 1)) {
return endColor
}else if current > (per * CGFloat(i + 1)) {
if i == cs.count - 2 {
return endColor
}
continue
}
}
}
}
return nil
}
}