渐变环形进度条

前言:
进了新公司,发现代码全都是第三方拼接的。虽然实现了功能,但定制很麻烦。以前单色的进度条,实现
简单,现在需要做到颜色渐变,于是就自己写了个控件。(实在不喜欢用第三方,除非必要的)。
实现原理:
其实原理很简单,只是在单色的基础上,把圆环分成多个小段,然后逐个分配颜色。
拓展实现:
控件添加了文字进度的显示;
控件还增加了从无到有的一个动画效果,其实也就是不断绘图的过程。
代码实现: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
}

}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容