如何实现起点与终点均为315°的圆角矩形进度条?
解决圆角矩形进度条起点终点对齐问题
问题出在你混淆了路径长度百分比和角度的对应关系:strokeStart和strokeEnd是基于UIBezierPath的总长度百分比,而非直接的角度值。你的原始路径起点在顶部中心,即使设置strokeStart = 135/360,也只是对应路径总长度的37.5%位置,和你想要的315°位置不匹配。
要实现起点和终点都在315°位置,核心是让路径的起点直接落在目标位置,再通过strokeEnd控制描边范围:
解决方案步骤
- 重新构建
UIBezierPath,让路径从你想要的315°位置(左侧直线上端点)开始,顺时针绕圆角矩形一周后回到起点。 - 将
strokeStart设为0,确保描边从路径起点开始。 - 修正
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
相关产品推荐
相关产品推荐

