I'm trying to implement an animation similar to what you can see on the image: enter image description here

我使用核心图形和核心动画与UIBezierPath来实现这一点,但问题似乎是在CGPath的开始和结束(strokeStart总是需要小于strokeEnd,这样蛇就不会通过路径闭合点的动画).在这上面花了太多时间之后,我开始想,也许我用错了工具,任何建议都是受欢迎的.

以下是我用来生成图像的代码示例:

func animate() {
        let centerRectInRect = {(rect: CGRect, bounds: CGRect) -> CGRect in
            return CGRect(x: bounds.origin.x + ((bounds.width - rect.width) / 2.0),
                          y: bounds.origin.y + ((bounds.height - rect.height) / 2.0),
                          width: rect.width,
                          height: rect.height)
        }
        
        
        let shapeLayer = CAShapeLayer()
        shapeLayer.frame = centerRectInRect(CGRect(x: 0.0, y: 0.0, width: 200.0, height: 200.0), self.view.bounds)
        self.view.layer.addSublayer(shapeLayer)
        
        shapeLayer.strokeStart = 0.0
        shapeLayer.strokeEnd = 1.0
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.fillColor = UIColor.orange.withAlphaComponent(0.2).cgColor
        shapeLayer.lineWidth = 12.0
        
        let rect = shapeLayer.bounds
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 16, height: 16))
        path.append(UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 16, height: 16)))
        shapeLayer.path = path.cgPath

        let strokeStartAnim = CAKeyframeAnimation(keyPath: "strokeStart")
        strokeStartAnim.values = [0, 1]
        strokeStartAnim.keyTimes = [0, 1]
        strokeStartAnim.duration = 12.0
        strokeStartAnim.beginTime = 1.0
        strokeStartAnim.repeatCount = .infinity
        strokeStartAnim.calculationMode = .paced
        
        let strokeEndAnim = CAKeyframeAnimation(keyPath: "strokeEnd")
        strokeEndAnim.values = [0, 1]
        strokeEndAnim.keyTimes = [0, 1]
        strokeEndAnim.duration = 12.0
        strokeEndAnim.repeatCount = .infinity
        strokeEndAnim.calculationMode = .paced

        let groupAnim = CAAnimationGroup()
        groupAnim.animations = [strokeStartAnim, strokeEndAnim]
        groupAnim.isRemovedOnCompletion = false
        groupAnim.fillMode = .forwards
        groupAnim.duration = .greatestFiniteMagnitude
        shapeLayer.add(groupAnim, forKey: "AnimateSnake")
        
    }

推荐答案

我终于成功地实现了我想要的.我认为只用一个层是不可能的,所以我用了两个层,并将第二个层旋转了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()
//        }
    }
}

结果:

enter image description here

Ios相关问答推荐

我应该使用哪个UIButton发送事件在SWIFT上刷卡?

在iOS中使用Swift,即时更改应用语言后,立即改变外观会出现问题

使用 SwiftUI 显示多个 VNRecognizedObjectObservation 边界框时偏移量错误

如何以编程方式触发 SwiftUI DatePicker?

无法在 CollectionView 中禁用部分弹跳 - 组合布局

iOS - 判断开发者模式是否开启 (Swift)

UIView 倒置旋转时的zoom 问题

我需要二进制文件来进行 App Store 应用程序传输吗?

视觉扫描无法识别 iOS16 上的 GS1 条码特殊字符

PropertyWrapper 不存在的 Codable 属性的默认值

SwiftUI withAnimation 在视图出现之前不会启动

'DismissAction'类型的值没有成员'wrappedValue'在SwiftUI的Xcode中

撤销 Apple 登录令牌以进行帐户删除过程

如何仅更改一个视图的导航标题 colored颜色 ?

无法结合 UIImageView 旋转动画和 tableView 部分重新加载

Xcode 8,iOS 10 -为进程启动 WebFilter 日志(log)记录

用 PC 调试 ipad safari

以编程方式 Select UITextField 中的所有文本

'无效更新:第 0 节中的无效行数

如何在 Objective-C (iOS) 中的图像上写文本?