如何更新 toValue/fromValue 的 CABasicAnimation 以使其在更改时更流畅?

How to update toValue/fromValue of CABasicAnimation to be smoother when changed?

我已经将 2 个动画应用于 CAShapeLayer(我们将其命名为 pulseLayer),代码如下:

let scaledAnimation = CABasicAnimation(keyPath: "transform.scale.xy")
scaledAnimation.duration       = 0.75
scaledAnimation.repeatCount    = Float.infinity
scaledAnimation.autoreverses   = true
scaledAnimation.fromValue      = 4
scaledAnimation.toValue        = 4
scaledAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)

let heartBeatAnimation = CABasicAnimation(keyPath: "transform.scale.xy")
heartBeatAnimation.duration       = 0.75
heartBeatAnimation.repeatCount    = Float.infinity
heartBeatAnimation.autoreverses   = true
heartBeatAnimation.fromValue      = 1.0
heartBeatAnimation.toValue        = 1.2
heartBeatAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)

pulseLayer.add(heartBeatAnimation, forKey: "heartBeatAnimation")

heartBeatAnimation 开启的某个时候,我需要删除心跳动画并使用以下代码添加缩放动画:

pulseLayer.add(self.scaledAnimation, forKey: "scaledAnimation")
pulseLayer.opacity = 0.55
pulseLayer.removeAnimation(forKey: "heartBeatAnimation")

但是我在这两个动画之间没有任何平滑的过渡,即使 UIView.animate()

所以我试着只保留一个动画 heartBeatAnimation 并将其 toValue fromValue 更改为相同的值以获得与 scaledAnimation 相同的值,代码如下:

heartBeatAnimation.toValue = 4
heartBeatAnimation.fromValue = 4

在动画消失并且用户做了一些手势以启动动画后动画跳动时没有任何反应我得到了缩放的稳定动画...!

关于如何更新这些值以使缩放动画更平滑的任何想法!

试试看。完整的动画和两者之间的切换取决于 duration, count, fromValue and toValue 具有 UIView.animate 闭包

的 CABasicAnimation 属性
@IBOutlet var vwAnimation: UIView!
    let initialScale: CGFloat = 1.0
    let animatingScale: CGFloat = 2.0
    let finalScale: CGFloat = 3.0

    override func viewDidLoad() {
        super.viewDidLoad()
        addAnimation()
    }


    func addAnimation(){

        heartBeatAnimation()
        self.perform(#selector(self.switchAnimation), with: nil, afterDelay: 3.0)
    }

    @objc func switchAnimation(){

        UIView.animate(withDuration: 0.25, animations: {
            self.vwAnimation.layer.removeAnimation(forKey: "heartBeatAnimation")
            self.scaledAnimation()
            self.vwAnimation.layoutIfNeeded()
        }) { (isCompleted) in

        }
    }


    func scaledAnimation() -> Void {

        let scaledAnimation = CABasicAnimation(keyPath: "transform.scale.xy")
        scaledAnimation.duration       = 0.5
        scaledAnimation.repeatCount    = 0.5
        scaledAnimation.autoreverses   = true
        scaledAnimation.fromValue      = initialScale
        scaledAnimation.toValue        = finalScale
        scaledAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
        vwAnimation.layer.add(scaledAnimation, forKey: "scaledAnimation")

        self.perform(#selector(self.adjustScale), with: nil, afterDelay: 0.5)
    }

    @objc func adjustScale(){
        self.vwAnimation.layer.removeAnimation(forKey: "scaledAnimation")
        let scaleTransform = CGAffineTransform(scaleX: finalScale, y: finalScale)
        vwAnimation.transform = scaleTransform
    }


    func heartBeatAnimation() -> Void {

        let heartBeatAnimation = CABasicAnimation(keyPath: "transform.scale.xy")
        heartBeatAnimation.duration       = 0.5
        heartBeatAnimation.repeatCount    = Float.infinity
        heartBeatAnimation.autoreverses   = true
        heartBeatAnimation.fromValue      = initialScale
        heartBeatAnimation.toValue        = animatingScale
        heartBeatAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
        vwAnimation.layer.add(heartBeatAnimation, forKey: "heartBeatAnimation")

    }

这是上面代码的结果,如果想要更改此结果,请告诉我:

不使用延迟等,而是使用 CATransaction 和 CASpringAnimations。

 import UIKit

class ViewController: UIViewController {

    var shapeLayer : CAShapeLayer!
    var button : UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        //add a shapelayer
        shapeLayer = CAShapeLayer()
        shapeLayer.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width/4, height: self.view.bounds.width/4)
        shapeLayer.position = self.view.center
        shapeLayer.path = drawStarPath(frame: shapeLayer.bounds).cgPath
        let color = UIColor(red: 0.989, green: 1.000, blue: 0.000, alpha: 1.000)
        shapeLayer.fillColor = color.cgColor
        self.view.layer.addSublayer(shapeLayer)


        //button for action
        button = UIButton(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width - 20, height: 50))
        button.center = self.view.center
        button.center.y = self.view.bounds.height - 70
        button.addTarget(self, action: #selector(ViewController.pressed(sender:)), for: .touchUpInside)
        button.setTitle("Animate", for: .normal)
        button.setTitleColor(.blue, for: .normal)

        self.view.addSubview(button)

  }

  func pressed(sender:UIButton) {

        if button.titleLabel?.text == "Reset Layer"{
            reset()
            return
        }


        //perform Animation
        button.isEnabled = false
        button.setTitle("Animating...", for: .normal)

        let heartBeatAnimation = CABasicAnimation(keyPath: "transform.scale.xy")
        heartBeatAnimation.duration       = 0.5
        heartBeatAnimation.repeatCount    = 2
        heartBeatAnimation.autoreverses   = true
        heartBeatAnimation.fromValue      = 1.0
        heartBeatAnimation.toValue        = 2.0
        heartBeatAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        CATransaction.begin()
        CATransaction.setCompletionBlock { 
            //call when finished
            [weak self] in
            if let vc = self{
                vc.scaleUpToComplete()
            }

        }
        shapeLayer.add(heartBeatAnimation, forKey: "beatAnimation")
        CATransaction.commit()
    }

    func scaleUpToComplete(){

        let scaledAnimation = CASpringAnimation(keyPath: "transform.scale.xy")
        scaledAnimation.duration       = 0.7
        scaledAnimation.fromValue      = 1.0
        scaledAnimation.toValue        = 2.0
        scaledAnimation.damping = 8.0
        scaledAnimation.initialVelocity = 9
        scaledAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        CATransaction.begin()
        CATransaction.setCompletionBlock { 
            //set transform
            [weak self] in
            if let vc = self{
                let scaleTransform = CATransform3DScale(CATransform3DIdentity, 2.0, 2.0, 1)
                vc.shapeLayer.transform = scaleTransform
                vc.shapeLayer.removeAllAnimations()

                //button title and enabled
                vc.button.isEnabled = true
                vc.button.setTitle("Reset Layer", for: .normal)

            }
        }
        shapeLayer.add(scaledAnimation, forKey: "scaleUp")
        CATransaction.commit()
    }


    func reset(){
        let scaledAnimation = CASpringAnimation(keyPath: "transform.scale.xy")
        scaledAnimation.duration       = 0.7
        scaledAnimation.fromValue      = 2.0
        scaledAnimation.toValue        = 1.0
        scaledAnimation.damping = 8.0
        scaledAnimation.initialVelocity = 9
        scaledAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        CATransaction.begin()
        CATransaction.setCompletionBlock {
            //set transform
            [weak self] in
            if let vc = self{
                let scaleTransform = CATransform3DScale(CATransform3DIdentity, 1.0, 1.0, 1)
                vc.shapeLayer.transform = scaleTransform
                vc.shapeLayer.removeAllAnimations()
                vc.button.setTitle("Animate", for: .normal)
            }
        }
        shapeLayer.add(scaledAnimation, forKey: "scaleDown")
        CATransaction.commit()

    }

    func drawStarPath(frame: CGRect = CGRect(x: 0, y: 0, width: 140, height: 140)) ->UIBezierPath{
        //// Star Drawing
        let starPath = UIBezierPath()
        starPath.move(to: CGPoint(x: frame.minX + 0.50000 * frame.width, y: frame.minY + 0.21071 * frame.height))
        starPath.addLine(to: CGPoint(x: frame.minX + 0.60202 * frame.width, y: frame.minY + 0.35958 * frame.height))
        starPath.addLine(to: CGPoint(x: frame.minX + 0.77513 * frame.width, y: frame.minY + 0.41061 * frame.height))
        starPath.addLine(to: CGPoint(x: frame.minX + 0.66508 * frame.width, y: frame.minY + 0.55364 * frame.height))
        starPath.addLine(to: CGPoint(x: frame.minX + 0.67004 * frame.width, y: frame.minY + 0.73404 * frame.height))
        starPath.addLine(to: CGPoint(x: frame.minX + 0.50000 * frame.width, y: frame.minY + 0.67357 * frame.height))
        starPath.addLine(to: CGPoint(x: frame.minX + 0.32996 * frame.width, y: frame.minY + 0.73404 * frame.height))
        starPath.addLine(to: CGPoint(x: frame.minX + 0.33492 * frame.width, y: frame.minY + 0.55364 * frame.height))
        starPath.addLine(to: CGPoint(x: frame.minX + 0.22487 * frame.width, y: frame.minY + 0.41061 * frame.height))
        starPath.addLine(to: CGPoint(x: frame.minX + 0.39798 * frame.width, y: frame.minY + 0.35958 * frame.height))
        return starPath
    }
}