【问题标题】:Glitch at start of CABasicAnimationCABasicAnimation 开始时的故障
【发布时间】:2020-03-30 17:04:34
【问题描述】:

我有一个缩放/脉冲动画,它在一个 for 循环中的 cgPaths 上工作,如下所示。此代码有效,但仅当您将circleLayer 添加到subLayer 时将animatedLayers 附加到circleLayer 路径并且这会在动画(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")
    }
}

【问题讨论】:

    标签: swift cashapelayer cabasicanimation


    【解决方案1】:

    关键问题是您的动画会在 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")
        }
    }
    

    【讨论】:

    • 感谢您的解决方案,设置调度延迟是问题所在。也感谢您的设置和结构指南。
    • 哦,是的,我通常只是投赞成票,所以我认为系统会根据获得的票数自动将特定答案标记为正确。谢谢你。
    【解决方案2】:

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

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

    【讨论】:

    • 我收到错误 Use of unresolved identifier 'animatedLayer'。是不是因为语句不在 for 循环中?
    • 是的,那离我很远。你应该完全关闭这条线。无论如何,您的动画似乎正在设置圆圈的颜色。如果它没有将颜色设置为动画的一部分——如果它只使用半透明/alpha,请在动画中设置圆的颜色。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-31
    • 2017-11-18
    相关资源
    最近更新 更多