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

如何实现起点与终点均为315°的圆角矩形进度条?

解决圆角矩形进度条起点终点对齐问题

问题出在你混淆了路径长度百分比和角度的对应关系:strokeStart和strokeEnd是基于UIBezierPath的总长度百分比,而非直接的角度值。你的原始路径起点在顶部中心,即使设置strokeStart = 135/360,也只是对应路径总长度的37.5%位置,和你想要的315°位置不匹配。

要实现起点和终点都在315°位置,核心是让路径的起点直接落在目标位置,再通过strokeEnd控制描边范围:

解决方案步骤

  1. 重新构建UIBezierPath,让路径从你想要的315°位置(左侧直线上端点)开始,顺时针绕圆角矩形一周后回到起点。
  2. 将strokeStart设为0,确保描边从路径起点开始。
  3. 修正layoutSubviews方法,避免view尺寸变化时路径错位。

修改后的完整代码

class ProgressBarView: UIView {
    
    let progressLayer = CAShapeLayer()
    let cornerRadius: CGFloat = 20
    private var currentProgress: CGFloat = 0
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupProgressLayer()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupProgressLayer()
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        // 尺寸变化时重新生成路径
        progressLayer.removeFromSuperlayer()
        setupProgressLayer()
        progressLayer.strokeEnd = currentProgress
    }
    
    private func setupProgressLayer() {
        progressLayer.lineWidth = 6
        progressLayer.fillColor = nil
        progressLayer.strokeColor = Constants.style.offWhite.cgColor
        progressLayer.strokeStart = 0 // 起点为路径开始位置
        progressLayer.lineCap = .round
        
        let lineWidth: CGFloat = 6
        // 目标起点:315°位置(左侧直线上端点)
        let startPoint = CGPoint(x: lineWidth / 2, y: lineWidth / 2 + cornerRadius)
        
        let progressPath = UIBezierPath()
        progressPath.move(to: startPoint)
        
        // 左上角圆弧:从左侧上端点到顶部左端点
        let topLeftArcCenter = CGPoint(x: lineWidth / 2 + cornerRadius, y: lineWidth / 2 + cornerRadius)
        progressPath.addArc(withCenter: topLeftArcCenter, radius: cornerRadius, startAngle: .pi, endAngle: -.pi/2, clockwise: true)
        
        // 顶部直线:从顶部左端点到顶部右端点
        let topRightLineEnd = CGPoint(x: bounds.width - lineWidth / 2 - cornerRadius, y: lineWidth / 2)
        progressPath.addLine(to: topRightLineEnd)
        
        // 右上角圆弧:从顶部右端点到右侧上端点
        let topRightArcCenter = CGPoint(x: bounds.width - lineWidth / 2 - cornerRadius, y: lineWidth / 2 + cornerRadius)
        progressPath.addArc(withCenter: topRightArcCenter, radius: cornerRadius, startAngle: -.pi/2, endAngle: 0, clockwise: true)
        
        // 右侧直线:从右侧上端点到右侧下端点
        let rightBottomLineEnd = CGPoint(x: bounds.width - lineWidth / 2, y: bounds.height - lineWidth / 2 - cornerRadius)
        progressPath.addLine(to: rightBottomLineEnd)
        
        // 右下角圆弧:从右侧下端点到底部右端点
        let bottomRightArcCenter = CGPoint(x: bounds.width - lineWidth / 2 - cornerRadius, y: bounds.height - lineWidth / 2 - cornerRadius)
        progressPath.addArc(withCenter: bottomRightArcCenter, radius: cornerRadius, startAngle: 0, endAngle: .pi/2, clockwise: true)
        
        // 底部直线:从底部右端点到底部左端点
        let bottomLeftLineEnd = CGPoint(x: lineWidth / 2 + cornerRadius, y: bounds.height - lineWidth / 2)
        progressPath.addLine(to: bottomLeftLineEnd)
        
        // 左下角圆弧:从底部左端点到左侧下端点
        let bottomLeftArcCenter = CGPoint(x: lineWidth / 2 + cornerRadius, y: bounds.height - lineWidth / 2 - cornerRadius)
        progressPath.addArc(withCenter: bottomLeftArcCenter, radius: cornerRadius, startAngle: .pi/2, endAngle: .pi, clockwise: true)
        
        // 左侧直线:回到起点
        progressPath.addLine(to: startPoint)
        progressPath.close()
        
        progressLayer.path = progressPath.cgPath
        layer.addSublayer(progressLayer)
    }
    
    func setProgress(_ progress: CGFloat) {
        currentProgress = max(0, min(1, progress)) // 限制进度在0-1区间
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.fromValue = progressLayer.strokeEnd
        animation.toValue = currentProgress
        animation.duration = 1
        animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
        progressLayer.add(animation, forKey: "progressAnimation")
        progressLayer.strokeEnd = currentProgress
    }
}

关键说明

  • 路径起点直接设为目标315°位置,确保strokeStart=0时描边从该点开始。
  • 重写layoutSubviews,在view尺寸变化时重新生成路径,避免圆角矩形变形。
  • 增加currentProgress变量保存当前进度,尺寸变化后恢复,避免进度丢失。

内容的提问来源于stack exchange,提问作者Chris

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 00:08:13