Swift实现沿圆角矩形路径的等长无限线条动画方法咨询
没问题,我来给你梳理一个完美解决这个需求的方案——你要的是一段固定长度的线条沿着圆角矩形路径无限循环滑动,而且全程保持长度不变对吧?之前用虚线动画之类的方案确实容易踩坑(比如圆角部分线条视觉长度会变形),下面这个自定义View的实现能精准控制效果:
实现沿圆角矩形路径的固定长度无限循环线条动画
核心思路是:先生成完整的圆角矩形路径,然后通过CADisplayLink驱动动画,实时截取路径上固定长度的子段进行绘制;当线条的起点走到路径终点时,自动从路径开头衔接,实现无缝循环。
步骤1:自定义动画承载View
先创建一个专门的UIView子类,用来管理路径、动画逻辑和线条绘制:
import UIKit class AnimatedFixedLengthLineView: UIView { // 可自定义的动画参数 var cornerRadius: CGFloat = 20 { didSet { refreshFullPath() } } var fixedLineLength: CGFloat = 80 { didSet { // 避免线条长度超过路径总长度 if fixedLineLength > fullPathLength { fixedLineLength = fullPathLength } updateCurrentLinePath() } } var lineWidth: CGFloat = 4 { didSet { shapeLayer.lineWidth = lineWidth } } var lineColor: UIColor = .systemBlue { didSet { shapeLayer.strokeColor = lineColor.cgColor } } // 内部核心组件 private let shapeLayer = CAShapeLayer() private var fullRoundedRectPath: UIBezierPath! private var fullPathLength: CGFloat = 0 private var currentProgress: CGFloat = 0 // 0~1,代表起点在路径上的位置比例 private let displayLink = CADisplayLink(target: self, selector: #selector(updateAnimationFrame)) override init(frame: CGRect) { super.init(frame: frame) setupBaseConfig() } required init?(coder: NSCoder) { super.init(coder: coder) setupBaseConfig() } private func setupBaseConfig() { shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.strokeEnd = 1.0 layer.addSublayer(shapeLayer) refreshFullPath() // 绑定屏幕刷新率驱动动画 displayLink.add(to: .main, forMode: .common) } override func layoutSubviews() { super.layoutSubviews() // 视图尺寸变化时重新生成路径 refreshFullPath() } }
步骤2:生成圆角矩形路径并计算总长度
我们需要确保路径预留线条宽度的边距,避免线条被视图裁切,同时计算路径总长度作为动画的基准:
extension AnimatedFixedLengthLineView { private func refreshFullPath() { // 留出线条宽度的边距,防止线条被裁切 let insetRect = bounds.insetBy(dx: lineWidth/2, dy: lineWidth/2) fullRoundedRectPath = UIBezierPath(roundedRect: insetRect, cornerRadius: cornerRadius) fullPathLength = fullRoundedRectPath.cgPath.length updateCurrentLinePath() } }
步骤3:核心动画逻辑——实时更新线条路径
这里通过CADisplayLink逐帧计算线条的起止位置,截取路径的固定长度子段绘制,实现循环滑动效果:
extension AnimatedFixedLengthLineView { @objc private func updateAnimationFrame() { // 控制动画速度:每秒移动40pt,可根据需求调整 let moveSpeed: CGFloat = 40 / 60 // 60对应屏幕刷新率 currentProgress += moveSpeed / fullPathLength // 超过1则重置进度,实现无限循环 if currentProgress > 1 { currentProgress -= 1 } updateCurrentLinePath() } private func updateCurrentLinePath() { let startDistance = currentProgress * fullPathLength var endDistance = startDistance + fixedLineLength // 处理跨路径首尾的情况 if endDistance > fullPathLength { // 分两段截取路径:从起点到路径末尾 + 从路径开头到剩余长度 let firstSegment = fullRoundedRectPath.cgPath.copy(dashingWithPhase: startDistance, lengths: [fullPathLength - startDistance, fullPathLength]) let secondSegment = fullRoundedRectPath.cgPath.copy(dashingWithPhase: 0, lengths: [endDistance - fullPathLength, fullPathLength]) let combinedPath = UIBezierPath() combinedPath.append(UIBezierPath(cgPath: firstSegment)) combinedPath.append(UIBezierPath(cgPath: secondSegment)) shapeLayer.path = combinedPath.cgPath } else { // 正常情况:直接截取固定长度的子路径 let targetPath = fullRoundedRectPath.cgPath.copy(dashingWithPhase: startDistance, lengths: [fixedLineLength, fullPathLength]) shapeLayer.path = targetPath } } }
步骤4:在项目中使用这个View
在ViewController里直接添加配置即可:
class DemoViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white let animatedLineView = AnimatedFixedLengthLineView(frame: CGRect(x: 50, y: 200, width: 300, height: 200)) animatedLineView.cornerRadius = 30 animatedLineView.fixedLineLength = 100 animatedLineView.lineWidth = 5 animatedLineView.lineColor = .systemOrange view.addSubview(animatedLineView) } }
关键细节说明
- 固定长度保障:通过
CGPath的copy(dashingWithPhase:)方法直接截取路径上的固定弧长段,不管是直线还是圆角曲线,线条的实际长度始终保持你设置的fixedLineLength值。 - 无缝循环:当线条终点超过路径总长度时,自动拆分路径为两段拼接,完全没有断点感。
- 性能优化:用
CADisplayLink和屏幕刷新率同步,动画流畅无卡顿,路径计算开销极低,不会影响主线程性能。
内容的提问来源于stack exchange,提问作者martinzly
相关产品推荐
相关产品推荐

