// // LNCircleProgressView.swift // Lanu // // Created by OneeChan on 2025/12/12. // import Foundation import UIKit import SnapKit class LNCircleProgressView: UIView { var lineWidth: CGFloat = 3.0 var trackColor: UIColor = UIColor.lightGray.withAlphaComponent(0.5) var progressColor: UIColor = .fill_5 private(set) var progress: CGFloat = 0.0 { didSet { guard progress >= 0, progress <= 1 else { self.progress = max(0, min(1, progress)) return } setNeedsDisplay() } } private var animationDuration: TimeInterval = 0.6 private let progressLayer = CAShapeLayer() private let trackLayer = CAShapeLayer() override init(frame: CGRect) { super.init(frame: frame) setupViews() } func setProgress(_ progress: CGFloat, animated: Bool = true) { let targetProgress = max(0, min(1, progress)) progressLayer.removeAllAnimations() if animated { // 创建进度动画 let animation = CABasicAnimation(keyPath: "strokeEnd") animation.fromValue = self.progressLayer.strokeEnd animation.toValue = targetProgress animation.duration = animationDuration animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) progressLayer.add(animation, forKey: "progressAnimation") progressLayer.strokeEnd = targetProgress } else { progressLayer.strokeEnd = targetProgress } self.progress = targetProgress } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() updateLayerFrames() } } extension LNCircleProgressView { private func setupViews() { trackLayer.fillColor = nil trackLayer.strokeColor = trackColor.cgColor trackLayer.lineWidth = lineWidth trackLayer.lineCap = .round layer.addSublayer(trackLayer) progressLayer.fillColor = nil progressLayer.strokeColor = progressColor.cgColor progressLayer.lineWidth = lineWidth progressLayer.lineCap = .round progressLayer.strokeEnd = 0 layer.addSublayer(progressLayer) } private func updateLayerFrames() { let center = CGPoint(x: bounds.midX, y: bounds.midY) let radius = min(bounds.width, bounds.height) / 2 - lineWidth / 2 // 创建圆形路径(从12点钟方向开始,顺时针绘制) let path = UIBezierPath( arcCenter: center, radius: radius, startAngle: -CGFloat.pi / 2, endAngle: 3 * CGFloat.pi / 2, clockwise: true ) trackLayer.path = path.cgPath trackLayer.frame = bounds progressLayer.path = path.cgPath progressLayer.frame = bounds } } #if DEBUG import SwiftUI struct LNCircleProgressViewPreview: UIViewRepresentable { func makeUIView(context: Context) -> some UIView { let container = UIView() container.backgroundColor = .black.withAlphaComponent(0.5) let view = LNCircleProgressView() container.addSubview(view) view.snp.makeConstraints { make in make.center.equalToSuperview() make.width.height.equalTo(30) } view.setProgress(0.5) return container } func updateUIView(_ uiView: UIViewType, context: Context) { } } #Preview(body: { LNCircleProgressViewPreview() }) #endif