You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建从顶部起始的带间隙环形分步进度条并实现动画?

问题描述

我需要创建一个带间隙的分步环形进度条,并给图层添加动画效果。目前效果已经实现,但进度条默认从右侧中心(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,因为环形路径是循环的)。只需要修改两处起始角度的初始化值即可:

  1. 修改sampleProgress方法中的初始startAngle:
    把var startAngle = 0.0改成:
var startAngle: CGFloat = -0.25
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 16:35:22