如何用CABasicAnimation实现指定加载指示器?
如何用CABasicAnimation实现指定加载指示器?
嘿,我懂你现在的困扰——用四个独立图层来做这个加载指示器不仅代码繁琐,还容易出现动画不同步的问题,其实咱们完全可以只用一个CAShapeLayer就搞定这个效果,不用搞那么多图层。下面我给你整理了优化后的实现方案,和你想要的效果完全匹配:
核心思路
我们用单个形状图层绘制完整的圆环路径,然后通过控制strokeStart和strokeEnd属性,配合**动画组(CAAnimationGroup)**来实现分段填充的效果,再加上旋转动画让整个加载器动起来,全程只用一个图层,代码更简洁也更容易维护。
完整实现代码
private func setupLoadingIndicator() { // 创建单个形状图层 let loadingLayer = CAShapeLayer() loadingLayer.strokeColor = UIColor.orange.cgColor loadingLayer.fillColor = UIColor.clear.cgColor loadingLayer.lineWidth = 5 loadingLayer.lineCap = .round loadingLayer.strokeEnd = 0 // 设置完整圆环路径(我们通过strokeStart/end控制显示分段) let radius = (bounds.height - loadingLayer.lineWidth) / 2 let fullCirclePath = UIBezierPath( arcCenter: CGPoint(x: bounds.width/2, y: bounds.height/2), radius: radius, startAngle: CGFloat.angle(progress: 0), endAngle: CGFloat.angle(progress: 1), clockwise: true ) loadingLayer.path = fullCirclePath.cgPath self.layer.addSublayer(loadingLayer) // 创建四段填充的动画组 let fillAnimations = CAAnimationGroup() fillAnimations.duration = 4 fillAnimations.repeatDuration = .infinity fillAnimations.timingFunction = CAMediaTimingFunction(name: .easeIn) var segmentAnimations = [CABasicAnimation]() // 为每个分段创建填充动画,设置不同的开始时间偏移 for segmentIndex in 0..<4 { let startProgress = CGFloat(segmentIndex) * 0.25 let endProgress = startProgress + 0.25 let strokeStartAnim = CABasicAnimation(keyPath: "strokeStart") strokeStartAnim.fromValue = startProgress strokeStartAnim.toValue = startProgress let strokeEndAnim = CABasicAnimation(keyPath: "strokeEnd") strokeEndAnim.fromValue = startProgress strokeEndAnim.toValue = endProgress // 每个分段动画延迟1秒开始,依次触发 strokeStartAnim.beginTime = Double(segmentIndex) strokeEndAnim.beginTime = Double(segmentIndex) segmentAnimations.append(strokeStartAnim) segmentAnimations.append(strokeEndAnim) } fillAnimations.animations = segmentAnimations loadingLayer.add(fillAnimations, forKey: "segmentFill") // 创建旋转动画,和填充动画同步 let rotationAnim = CABasicAnimation(keyPath: "transform.rotation.z") rotationAnim.duration = 4 rotationAnim.fromValue = CGFloat.angle(progress: 0) rotationAnim.toValue = CGFloat.angle(progress: 1) // 旋转一整圈,和四阶段填充同步 rotationAnim.repeatDuration = .infinity rotationAnim.timingFunction = CAMediaTimingFunction(name: .easeIn) loadingLayer.add(rotationAnim, forKey: "rotation") } private extension CGFloat { static var initialAngle: CGFloat = -(.pi / 2) static func angle(progress: CGFloat) -> CGFloat { .pi * 2 * progress + .initialAngle } }
关键细节解释
- 单图层优势:只用一个
CAShapeLayer,避免了多图层管理的麻烦,动画同步性更好。 - 分段填充控制:通过
strokeStart固定每个分段的起始位置,strokeEnd从起始位置动画到分段结束位置,每个分段的动画通过beginTime延迟1秒,实现依次亮起的效果。 - 动画同步:填充动画组总时长4秒(每段1秒),旋转动画同样设置为4秒旋转一整圈,保证动画节奏完全匹配。
- 圆角端点:
lineCap = .round确保分段的两端是圆角,和你想要的视觉效果一致。
如果你想要的是填充完四段后再旋转90度循环,只需要调整旋转动画的duration为1秒,toValue设为CGFloat.angle(progress: 0.25),同时把填充动画组的duration改为1秒,四段动画的beginTime分别设为0、0.25、0.5、0.75即可,灵活调整参数就能匹配不同的动画节奏~
备注:内容来源于stack exchange,提问作者Danil Kozyr
相关产品推荐
相关产品推荐

