如何创建从顶部起始的带间隙环形分步进度条并实现动画?
问题描述
我需要创建一个带间隙的分步环形进度条,并给图层添加动画效果。目前效果已经实现,但进度条默认从右侧中心(0位置)开始,而需求是从顶部中心起始,当前效果截图如下:

附上代码示例:
extension ViewController { func sampleProgress() { let totalSteps = 6 let frame = CGRect(x: 50, y: 50, width: 120, height: 120) let circlePath = UIBezierPath(ovalIn: frame) let gapSize: CGFloat = 0.0125 let segmentAngle: CGFloat = 0.167 // (1/totalSteps) var startAngle = 0.0 let lineWidth = 8.0 for index in 0 ... totalSteps { // Background layer let backgroundLayer = CAShapeLayer() backgroundLayer.strokeStart = startAngle backgroundLayer.strokeEnd = backgroundLayer.strokeStart + segmentAngle - gapSize backgroundLayer.path = circlePath.cgPath backgroundLayer.name = String(index) backgroundLayer.strokeColor = UIColor.lightGray.cgColor backgroundLayer.lineWidth = lineWidth backgroundLayer.lineCap = CAShapeLayerLineCap.butt backgroundLayer.fillColor = UIColor.clear.cgColor self.view.layer.addSublayer(backgroundLayer) // Foreground layer let foregroundLayer = CAShapeLayer() foregroundLayer.strokeStart = startAngle foregroundLayer.strokeEnd = backgroundLayer.strokeStart + segmentAngle - gapSize foregroundLayer.isHidden = true foregroundLayer.name = String(index) + String(index) foregroundLayer.path = circlePath.cgPath foregroundLayer.strokeColor = UIColor.green.cgColor foregroundLayer.lineWidth = lineWidth foregroundLayer.lineCap = CAShapeLayerLineCap.butt foregroundLayer.fillColor = UIColor.clear.cgColor self.view.layer.addSublayer(foregroundLayer) print("Start angle: \(startAngle)") startAngle = startAngle + segmentAngle } } func animateLayer(isAnimate: Bool, stepsToAnimate: Int) { let segmentAngle: CGFloat = (360 * 0.166) / 360 let gapSize: CGFloat = 0.0125 var startAngle = 0.0 for index in 0 ... stepsToAnimate { if let foregroundLayers = self.view.layer.sublayers { for animateLayer in foregroundLayers { if animateLayer.name == String(index) + String(index) { if index == stepsToAnimate && isAnimate { let animation = CABasicAnimation(keyPath: "strokeEnd") animation.fromValue = startAngle animation.toValue = startAngle + segmentAngle - gapSize animation.duration = 1.0 animateLayer.add(animation, forKey: "foregroundAnimation") animateLayer.isHidden = false } else { animateLayer.isHidden = false } startAngle = startAngle + segmentAngle } } } } } }
解决方案
问题出在Core Graphics环形路径的默认起点:UIBezierPath的ovalIn:方法创建的圆形路径,默认起始点是右侧中心(对应strokeStart=0的位置),而顶部中心对应的路径百分比是**-0.25**(或等价的0.75,因为环形路径是循环的)。只需要修改两处起始角度的初始化值即可:
- 修改
sampleProgress方法中的初始startAngle:
把var startAngle = 0.0改成:
var startAngle: CGFloat = -0.25
- 修改
animateLayer方法中的初始startAngle:
把var startAngle = 0.0改成:
var startAngle: CGFloat = -0.25
原理说明
CAShapeLayer的strokeStart和strokeEnd是基于路径总长度的百分比(范围0~1):
- 0 = 路径起点(右侧中心)
- 0.25 = 下侧中心
- 0.5 = 左侧中心
- 0.75 = 顶部中心(等价于-0.25,因为百分比是循环的)
设置初始startAngle为-0.25,就能让第一个进度分段从顶部中心开始,后续分段按顺时针方向依次排列,符合需求。
内容的提问来源于stack exchange,提问作者Sarath
相关产品推荐
相关产品推荐

