Swift 圆形进度条

demo 传送门
demo

效果图

HJProgress.gif

前言

ios 10.0

因为 UI 做了一些很别致的进度条效果图,而且在网上又找不到合适的三方库,自己在网上查查资料做了出来,然后整理了一下,分享给大家使用

首先,这个东西只是用贝赛尔曲线做出来的,并不是很难,做出来只是略微麻烦一点,网上也有很多资料,不过并不是很多,所以我就整理了一下,一是整理备份一下记录,二是分享给大家看下

1、绘制这个断断续续的进度条

UI 设计很漂亮,但是由于数据过多或过少,导致太难看,最后放弃


image.png

1.1 初始化一个贝塞尔直线

///贝塞尔直线
private lazy var path: UIBezierPath = {
    let bezierPath = UIBezierPath()
    bezierPath.move(to: CGPoint(x: 0, y: 0))
    bezierPath.addLine(to: CGPoint(x: bounds.size.width, y: 0))
    return bezierPath
}()

1.2 使用CAShapeLayer初始化底部背景线,和进度线

此处 lineDashPattern 是长度对比,一长一短线,我使用的是两个数据,多个的话,暂时没弄明白,有大佬了解的留个言,
建议:此处进度最好和你自己进度比率相同,要不然会出现上图的情况(一线两色)

///背景线
private lazy var backLayer: CAShapeLayer = {
    let layer = CAShapeLayer()
    layer.fillColor = UIColor.clear.cgColor
    layer.strokeColor = .green.cgColor
    layer.lineWidth = 2
    layer.lineDashPattern = [15, 3]
    layer.path = path.cgPath
    return layer
}()
///进度条线
private lazy var progressLayer: CAShapeLayer = {
    let layer = CAShapeLayer()
    layer.fillColor = UIColor.clear.cgColor
    layer.strokeColor = .red.cgColor
    layer.lineWidth = 2
    layer.lineDashPattern = [15, 3]
    layer.strokeStart = 0
    layer.strokeEnd = 0
    layer.path = path.cgPath
    return layer
}()

1.3 添加到视图上

直接设置 progressLayer.strokeEnd = progress 会显得突兀,我再此添加了一个动画,
后续此处就不一一说明

///数据
layer.addSublayer(backLayer)
layer.addSublayer(progressLayer)

///进度
public var progress: CGFloat = 0.0 {
    didSet {
        CATransaction.begin()
        CATransaction.setDisableActions(false)
        CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .linear))
        CATransaction.setAnimationDuration(2)
        progressLayer.strokeEnd = progress
        CATransaction.commit()
    }
}

2 普通圆形进度条

image.png

2.1首先开放公共参数

///背景颜色
public var backColor: UIColor = .gray
///进度条颜色
public var progressColor: UIColor = .red
///线宽
public var lineWidth: CGFloat = 2.0  
///圆直径
public var realWidth: CGFloat = 100
///圆起点角度。角度从水平右侧开始为0,顺时针为增加角度。直接传度数 如-90
public var startAngle: CGFloat = 0.5 * .pi
///圆结束角度。角度从水平右侧开始为0,顺时针为增加角度。直接传度数 如-90
public var endAngle: CGFloat = 0.5 * .pi
///动画时长
public var duration: CGFloat = 2

2.2 使用贝赛尔曲线画圆

设置中心点、半径(通过直径 - 宽度计算得来)、起始位置,结束位置

let radius = realWidth / 2 - lineWidth

///贝塞尔直线
private lazy var path: UIBezierPath = {
    let bezierPath = UIBezierPath(arcCenter: CGPoint(x: realWidth/2, y: realWidth/2),
                                  radius: radius,
                                  startAngle: startAngle,
                                  endAngle: 2 * .pi + endAngle,
                                  clockwise: true)
    return bezierPath
}()

2.2 使用CAShapeLayer初始化底部背景线,和进度线

然后 添加到视图,和设置进度我就不说了,上面都有, 至于是多少角度的圆形,设置起始角度和结束角度就可以了

///背景圆环
private lazy var backLayer: CAShapeLayer = {
    let layer = CAShapeLayer()
    layer.frame         = bounds
    layer.fillColor     = UIColor.clear.cgColor
    layer.lineWidth     = lineWidth
    layer.strokeColor   = backColor.cgColor
    layer.lineCap       = .round
    layer.path          = path.cgPath
    return layer
}()

///进度圆环
private lazy var progressLayer: CAShapeLayer = {
    let layer = CAShapeLayer()
    layer.frame         = bounds
    layer.fillColor     = UIColor.clear.cgColor
    layer.lineWidth     = lineWidth
    layer.strokeColor   = progressColor.cgColor
    layer.lineCap       = .round
    layer.path          = path.cgPath
    layer.strokeEnd     = 0
    return layer
}()

3 绘制渐变进度条

image.png

3.1首先开放渐变公共参数

相同参数我就不在一一叙述

///渐变颜色
public var colors: [CGColor] = [UIColor.red.cgColor, UIColor.green.cgColor]
///渐变色位置
public var locations: [NSNumber] = [0, 1]
///渐变色起始位置
public var startPoint: CGPoint = CGPoint(x: 0, y: 0)
///渐变色结束位置
public var endPoint: CGPoint = CGPoint(x: 1, y: 0)

3.2 使用CAGradientLayer设置渐变背景

///渐变背景
private lazy var gradientLayer: CAGradientLayer = {
    let layer = CAGradientLayer()
    layer.frame = bounds
    layer.colors =  colors
    layer.locations =  locations
    layer.startPoint =  startPoint
    layer.endPoint =  endPoint
    return layer
}()

3.3 把进度圆环放到渐变背景上

然后加载进度就可以了

gradientLayer.mask = progressLayer
layer.addSublayer(gradientLayer)

4 在进度条终点添加样式

image.png

4.1 添加终点样式

此处我使用了 UIView,你可以采用图片、渐变 view 等
在 layer.addSublayer(progressLayer) 后面放入,

addSubview(dot)
dot.isHidden = true

4.2 跟谁进度条速度加载

使用 CAAnimation 加载动画
整圆和非整圆这块加载我没给弄清楚,有了解的大佬请给予指出,虽然结果没错,但是两套逻辑总感觉很奇怪

private lazy var pointAnimation: CAAnimation = {
    let animation = CAKeyframeAnimation(keyPath: "position")
    animation.timingFunction = CAMediaTimingFunction(name: .linear)
    animation.fillMode = .forwards
    animation.calculationMode = CAAnimationCalculationMode.paced
    animation.isRemovedOnCompletion = false
    animation.duration = duration
    ///此处记得加载代理,不用执行方法
    animation.delegate = self
    
    var end = 2 * .pi * progress + startAngle
    ///非整圆
    if data.startAngle != data.endAngle {
        end = (2 * .pi - CGFloat(fabs(Double(endAngle))) - startAngle) * progress + startAngle
    }
    
    let imagePath = UIBezierPath(arcCenter: CGPoint(x: realWidth/2, y: realWidth/2),
                                 radius: radius,
                                 startAngle: startAngle,
                                 endAngle: end,
                                 clockwise: true)
    animation.path = imagePath.cgPath
    return animation
}()

执行加载动画, 此处跟谁到 progress 设置进度后面即可

dot.isHidden = false
dot.layer.add(pointAnimation, forKey: "pointAnimation")

5 仪表盘进度条样式

image.png

5.1 公开参数

此处总长度是圆周长长度,这个切记要计算好,否则最后一根线会有问题

///线间距 间断进度条专属  直线计算长度,圆计算周长
public var lineDashPattern: [NSNumber] = [15 , 5]

5.2 初始化贝塞尔曲线,CAShapeLayer背景和进度条

///贝塞尔直线
private lazy var path: UIBezierPath = {
    let bezierPath = UIBezierPath(arcCenter: CGPoint(x: data.realWidth / 2, y: data.realWidth / 2),
                                  radius: radius,
                                  startAngle: startAngle,
                                  endAngle: 2 * .pi + endAngle,
                                  clockwise: true)
        
    return bezierPath
}()
///背景
private lazy var backLayer: CAShapeLayer = {
    let layer = CAShapeLayer()
    layer.frame         = bounds
    layer.fillColor     = UIColor.clear.cgColor
    layer.lineWidth     = lineWidth
    layer.strokeColor   = backColor.cgColor
    layer.path          = cgPath
    layer.lineDashPattern = lineDashPattern
    
    return layer
}()
///进度条
private lazy var progressLayer: CAShapeLayer = {
    let layer = CAShapeLayer()
    layer.frame         = bounds
    layer.fillColor     = UIColor.clear.cgColor
    layer.lineWidth     = lineWidth
    layer.strokeColor   = progressColor.cgColor
    layer.path          = path.cgPath
    layer.strokeEnd     = 0
    layer.lineDashPattern = lineDashPattern
    
    return layer
}() 

添加和设置进度和之前一样,我就不一一赘述

下面就说些我封装的进度条使用方法

1、通过设置 type 类型,添加各种类型的进度条

let newView = HJProgress(frame: CGRect(x: 10, y: 110, width: 120, height: 120))
newView.type = .circle

2、通过设置HJData() 设置各种参数,添加数据之后,才会出现进度条

var data = HJData()
data.realWidth = 120
data.lineWidth = 2
newView.data = data

3、通过设置progress 加载进度条

具体例子看我上面的 demo 吧,欢迎来提建议

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

推荐阅读更多精彩内容