CABasicAnimation 开始时出现故障

Glitch at start of CABasicAnimation

我有一个 scaling/pulsing 动画,它在 for 循环中处理 cgPaths,如下所示。此代码有效,但仅当您将 animatedLayers 附加到 circleLayer 路径时,当 circleLayer 已添加到 subLayer 并且这会创建一个静态圆(类似故障) 在动画 (DispatchQueue) 开始之前...

...
self.layer.addSublayer(animatedLayer)
animatedLayers.append(animatedLayer)
...

是否可以将带参数的 CAShapeLayer 添加到子层?如果没有,有什么推荐的替代方案吗?

import UIKit
import Foundation

@IBDesignable
class AnimatedCircleView: UIView {

    // MARK: - Initializers

    var animatedLayers = [CAShapeLayer]()

    // MARK: - Methods

    override func draw(_ rect: CGRect) {

        // Animated circle
        for _ in 0...3 {
            let animatedPath = UIBezierPath(arcCenter: .zero, radius: self.layer.bounds.size.width / 2.3,
            startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
            let animatedLayer = CAShapeLayer()

            animatedLayer.path = animatedPath.cgPath
            animatedLayer.strokeColor = UIColor.black.cgColor
            animatedLayer.lineWidth = 0
            animatedLayer.fillColor = UIColor.gray.cgColor
            animatedLayer.lineCap = CAShapeLayerLineCap.round
            animatedLayer.position = CGPoint(x: self.layer.bounds.size.width / 2, y: self.layer.bounds.size.width / 2)
            self.layer.addSublayer(animatedLayer)
            animatedLayers.append(animatedLayer)
        }

        // Dispatch animation for circle _ 0...3
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            self.animateCircle(index: 0)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                self.animateCircle(index: 1)
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                    self.animateCircle(index: 2)
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
                        self.animateCircle(index: 3)
                    }
                }
            }
        }
    }


    func animateCircle(index: Int) {

        let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
        scaleAnimation.duration = 1.8
        scaleAnimation.fromValue = 0
        scaleAnimation.toValue = 1
        scaleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
        scaleAnimation.repeatCount = Float.infinity
        animatedLayers[index].add(scaleAnimation, forKey: "scale")

        let opacityAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
        opacityAnimation.duration = 1.8
        opacityAnimation.fromValue = 0.7
        opacityAnimation.toValue = 0
        opacityAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
        opacityAnimation.repeatCount = Float.infinity
        animatedLayers[index].add(opacityAnimation, forKey: "opacity")
    }
}

您是否尝试过在动画开始时将填充颜色设为透明,然后将其变为灰色?

    // Animated circle
    for _ in 0...3 {
        let animatedPath = UIBezierPath(arcCenter: .zero, radius: self.layer.bounds.size.width / 2.3,
        startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
        let animatedLayer = CAShapeLayer()

        animatedLayer.path = animatedPath.cgPath
        animatedLayer.strokeColor = UIColor.black.cgColor
        animatedLayer.lineWidth = 0
        animatedLayer.fillColor = UIColor.clear.cgColor
        animatedLayer.lineCap = CAShapeLayerLineCap.round
        animatedLayer.position = CGPoint(x: self.layer.bounds.size.width / 2, y: self.layer.bounds.size.width / 2)
        self.layer.addSublayer(animatedLayer)
        animatedLayers.append(animatedLayer)
    }

    // Dispatch animation for circle _ 0...3
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {

        self.animateCircle(index: 0)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
            self.animateCircle(index: 1)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                self.animateCircle(index: 2)
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
                    self.animateCircle(index: 3)
                }
            }
        }
    }
}

关键问题是您的动画开始时有交错的延迟,在 0.1 到 1.0 秒之间。在最后一个动画开始之前,该层只是坐在那里,全尺寸和 100% 不透明度。

由于您将变换比例设置为从 0 到 1 的动画,我建议将起始变换设置为 0(或将 opacity 更改为 0)。然后在他们各自的动画开始之前你不会看到他们坐在那里。

其他一些观察结果:

  • draw(_:)不是添加图层、启动动画等的正确位置。此方法可能会被调用多次,并且应该代表给定时间点的视图。我会退休 draw(_:) 是不是开始这个​​的地方不合适而且你根本不需要这个方法。

  • 你在 0.1 秒后开始你的第一个动画。为什么不立即开始呢?

  • 您应该通过将 pathposition 属性的设置推迟到 layoutSubviews 来处理帧调整。

因此:

@IBDesignable
class AnimatedCircleView: UIView {
    private var animatedLayers = [CAShapeLayer]()

    override init(frame: CGRect = .zero) {
        super.init(frame: frame)
        configure()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        configure()
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        let path = UIBezierPath(arcCenter: .zero, radius: bounds.width / 2.3, startAngle: 0, endAngle: 2 * .pi, clockwise: true)

        for animatedLayer in animatedLayers {
            animatedLayer.path = path.cgPath
            animatedLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
        }
    }
}

// MARK: - Methods

private extension AnimatedCircleView {
    func configure() {
        for _ in 0...3 {
            let animatedLayer = CAShapeLayer()
            animatedLayer.strokeColor = UIColor.black.cgColor
            animatedLayer.lineWidth = 0
            animatedLayer.fillColor = UIColor.gray.cgColor
            animatedLayer.lineCap = .round
            animatedLayer.transform = CATransform3DMakeScale(0, 0, 1)
            layer.addSublayer(animatedLayer)
            animatedLayers.append(animatedLayer)
        }

        self.animateCircle(index: 0)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            self.animateCircle(index: 1)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                self.animateCircle(index: 2)
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                    self.animateCircle(index: 3)
                }
            }
        }
    }

    func animateCircle(index: Int) {
        let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
        scaleAnimation.duration = 1.8
        scaleAnimation.fromValue = 0
        scaleAnimation.toValue = 1
        scaleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
        scaleAnimation.repeatCount = .greatestFiniteMagnitude
        animatedLayers[index].add(scaleAnimation, forKey: "scale")

        let opacityAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
        opacityAnimation.duration = 1.8
        opacityAnimation.fromValue = 0.7
        opacityAnimation.toValue = 0
        opacityAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
        opacityAnimation.repeatCount = .greatestFiniteMagnitude
        animatedLayers[index].add(opacityAnimation, forKey: "opacity")
    }
}