我终于成功地实现了我想要的.我认为只用一个层是不可能的,所以我用了两个层,并将第二个层旋转了180度,然后同步了动画,使它们重叠在一起,从而产生了只有一个笔划是动画的效果. prize 额度上限可以从CAShapeLayerLineCap
个选项中 Select .
import UIKit
class RoundedRectActivityIndicator: UIView {
private var shapeLayer: CAShapeLayer!
private var progressLayer1: CAShapeLayer!
private var progressLayer2: CAShapeLayer!
let cornerRadius: CGFloat
let lineWidth: CGFloat
private(set) var segmentLength: CGFloat
let lineCap: CAShapeLayerLineCap
private var readyForDrawing = false
private(set) var isAnimating = false
private var strokeColor: UIColor
private var strokeBackgroundColor: UIColor
private var animationDuration: CFTimeInterval
private var timeOffset: CFTimeInterval
private var automaticStart: Bool
required init(strokeColor: UIColor,
strokeBackgroundColor: UIColor = .clear,
cornerRadius: CGFloat = 0.0,
lineWidth: CGFloat = 4.0,
lineCap: CAShapeLayerLineCap = .round,
segmentLength: CGFloat = 0.4,
duration: CFTimeInterval,
timeOffset: CFTimeInterval = 0.0,
automaticStart: Bool = true) {
self.strokeColor = strokeColor
self.strokeBackgroundColor = strokeBackgroundColor
self.cornerRadius = cornerRadius
self.lineWidth = lineWidth
self.lineCap = lineCap
self.segmentLength = segmentLength
self.animationDuration = duration
self.timeOffset = timeOffset
self.automaticStart = automaticStart
super.init(frame: CGRect.zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
if !readyForDrawing {
firstTimeSetup()
}
if !isAnimating && automaticStart {
startAnimating()
}
}
private func firstTimeSetup() {
shapeLayer = newShapeLayer(rectangle: bounds)
shapeLayer.strokeColor = strokeBackgroundColor.cgColor
shapeLayer.strokeStart = 0
shapeLayer.strokeEnd = 1
layer.addSublayer(shapeLayer)
progressLayer1 = newShapeLayer(rectangle: bounds, lineCap: lineCap)
progressLayer1.strokeColor = strokeColor.cgColor
progressLayer2 = newShapeLayer(rectangle: bounds, lineCap: lineCap, rotation: 180)
progressLayer2.strokeColor = strokeColor.cgColor
readyForDrawing = true
}
private func newShapeLayer(rectangle: CGRect,
fillColor: UIColor = .clear,
lineCap: CAShapeLayerLineCap = .butt,
rotation: CGFloat = 0) -> CAShapeLayer {
let layer = CAShapeLayer()
let path = newPath(rectangle: rectangle, cornerRadius: cornerRadius, rotation: rotation)
layer.path = path.cgPath
layer.lineWidth = lineWidth
layer.fillColor = fillColor.cgColor
layer.lineCap = lineCap
return layer
}
private func newPath(rectangle: CGRect, cornerRadius: CGFloat, rotation: CGFloat = 0) -> UIBezierPath {
let path = UIBezierPath(roundedRect: rectangle, cornerRadius: cornerRadius)
path.rotate(degree: rotation)
return path
}
func startAnimating() {
isAnimating = true
layer.addSublayer(progressLayer1)
progressLayer2.strokeStart = 0
progressLayer2.strokeEnd = 0
layer.addSublayer(progressLayer2)
let strokeEndAnimation1 = CAKeyframeAnimation(keyPath: "strokeEnd")
strokeEndAnimation1.values = [0, 1]
strokeEndAnimation1.keyTimes = [0, 1]
let strokeStartAnimation1 = CAKeyframeAnimation(keyPath: "strokeStart")
strokeStartAnimation1.values = [0, 1]
strokeStartAnimation1.keyTimes = [0, 1]
strokeStartAnimation1.beginTime = animationDuration * segmentLength
let animationGroup1 = CAAnimationGroup()
animationGroup1.animations = [strokeEndAnimation1, strokeStartAnimation1]
animationGroup1.isRemovedOnCompletion = false
animationGroup1.duration = animationDuration
animationGroup1.fillMode = .forwards
animationGroup1.repeatCount = .infinity
animationGroup1.timeOffset = timeOffset
progressLayer1.add(animationGroup1, forKey: "animationGroup1")
let strokeEndAnimation2 = CAKeyframeAnimation(keyPath: "strokeEnd")
strokeEndAnimation2.values = [0, 1]
strokeEndAnimation2.keyTimes = [0, 1]
let strokeStartAnimation2 = CAKeyframeAnimation(keyPath: "strokeStart")
strokeStartAnimation2.values = [0, 1]
strokeStartAnimation2.keyTimes = [0, 1]
strokeStartAnimation2.beginTime = animationDuration * segmentLength
let animationGroup2 = CAAnimationGroup()
animationGroup2.animations = [strokeEndAnimation2, strokeStartAnimation2]
animationGroup2.isRemovedOnCompletion = false
animationGroup2.duration = animationDuration
animationGroup2.fillMode = .forwards
animationGroup2.repeatCount = .infinity
animationGroup2.beginTime = CACurrentMediaTime() + animationDuration / 2
animationGroup2.timeOffset = timeOffset
progressLayer2.add(animationGroup2, forKey: "animationGroup2")
}
func completeProgress() {
progressLayer1.removeAllAnimations()
progressLayer2.removeAllAnimations()
progressLayer1.strokeStart = 0
progressLayer1.strokeEnd = 1
}
}
extension UIBezierPath {
func rotate(degree: CGFloat) {
let bounds: CGRect = self.cgPath.boundingBox
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radians = degree / 180.0 * .pi
var transform: CGAffineTransform = .identity
transform = transform.translatedBy(x: center.x, y: center.y)
transform = transform.rotated(by: radians)
transform = transform.translatedBy(x: -center.x, y: -center.y)
self.apply(transform)
}
}
您可以这样使用它:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let segmentLenth = 0.4
let duration: CFTimeInterval = 10
let timeOffset: CFTimeInterval = segmentLenth * duration // or 0 if you don't mind it starting from the top left
let indicator = RoundedRectActivityIndicator(strokeColor: .red,
strokeBackgroundColor: .orange.withAlphaComponent(0.2),
cornerRadius: 6,
lineWidth: 6,
segmentLength: segmentLenth,
duration: duration,
timeOffset: timeOffset)
indicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(indicator)
NSLayoutConstraint.activate([
indicator.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 50),
indicator.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -50),
indicator.topAnchor.constraint(equalTo: view.topAnchor, constant: 350),
indicator.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -350)
])
// DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(8)) {
// indicator.completeProgress()
// }
}
}
结果: