ios UIView swift边框中的蛇形动画

rmbxnbpk  于 2023-06-25  发布在  iOS
关注(0)|答案(1)|浏览(140)

bounty还有4天到期。回答此问题可获得+100声望奖励。Saranjith正在寻找一个规范的答案:用于此用例的工作代码,支持无限循环。

如何在Swift iOS中制作蛇形边框动画

随着吹代码,它不继续动画。一旦它完成了从另一个位置开始。注意:我需要支持矩形的圆角半径。
当前代码:

borderShapeLayer.path = UIBezierPath(roundedRect: contentView.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 15, height: 15)).cgPath

    borderShapeLayer.fillColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0).cgColor
    borderShapeLayer.strokeColor = #colorLiteral(red: 1, green: 0, blue: 0, alpha: 1).cgColor
    borderShapeLayer.lineWidth = 5
    borderShapeLayer.strokeStart = 0.8

    let startAnimation = CABasicAnimation(keyPath: "strokeStart")
    startAnimation.fromValue = 0
    startAnimation.toValue = 0.8

    let endAnimation = CABasicAnimation(keyPath: "strokeEnd")
    endAnimation.fromValue = 0.2
    endAnimation.toValue = 1.0

    let animation = CAAnimationGroup()
    animation.animations = [startAnimation, endAnimation]
    animation.duration = 20
    animation.repeatCount = .infinity
    borderShapeLayer.add(animation, forKey: "MyAnimation")
    
    contentView.layer.addSublayer(borderShapeLayer)
chhkpiq4

chhkpiq41#

我需要实现一个类似的解决方案,并提出了这个方案。

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()
//        }
    }
}

结果:

相关问题