核心动画 - 修改动画属性

3
我有动画。
 func startRotate360() {
    let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
    rotation.fromValue = 0
    rotation.toValue =  Double.pi * 2
    rotation.duration = 1
    rotation.isCumulative = true
    rotation.repeatCount = Float.greatestFiniteMagnitude
    self.layer.add(rotation, forKey: "rotationAnimation")
}

我希望能够通过将动画的重复次数设置为1来停止动画,以便完成当前旋转(不能简单地删除动画,因为这样看起来不好)。我尝试了以下方法:
func stopRotate360() {
    self.layer.animation(forKey: "rotationAnimation")?.repeatCount = 1
}

但我遇到了崩溃并在控制台中看到以下信息:

试图修改只读动画

如何访问可写属性?


请告诉我我的回答是否解决了您的问题。 - Reinier Melian
3
我有同感。哈哈。 - agibson007
请问您能否给我们关于我们的回答提供任何反馈?这是您的问题。 - Reinier Melian
我认为两个答案都很好,我真的不知道该接受哪一个。我想让读者通过点赞来决定。 - Alexey K
3个回答

6

试一试吧。实际上,您可以更改正在进行的CAAnimations。有很多方法。这是最快/最简单的方法。您甚至可以完全停止动画,并在用户甚至没有注意到的情况下恢复它。

您可以看到启动动画功能以及停止功能。开始动画看起来与您的类似,而停止则从演示层中获取当前旋转并创建一个旋转直到完成的动画。我还平滑了持续时间,使其成为基于当前旋转z到基于运行动画的完整旋转所需时间的百分比。然后,我使用重复计数删除动画并添加新动画。您可以看到视图平稳地旋转到最终位置并停止。您会明白的。将其放入并运行它,看看您的想法。按按钮启动它,再次按下它以查看完成旋转并停止。

import UIKit

class ViewController: UIViewController {

    var animationView = UIView()
    var button = UIButton()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        animationView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
        animationView.backgroundColor = .green
        animationView.center = view.center
        self.view.addSubview(animationView)

        let label = UILabel(frame: animationView.bounds)
        label.text = "I Spin"
        animationView.addSubview(label)


        button = UIButton(frame: CGRect(x: 20, y: animationView.frame.maxY + 60, width: view.bounds.width - 40, height: 40))
        button.setTitle("Animate", for: .normal)
        button.setTitleColor(.blue, for: .normal)
        button.addTarget(self, action: #selector(ViewController.pressed), for: .touchUpInside)
        self.view.addSubview(button)

    }

    func pressed(){

        if let title = button.titleLabel?.text{
            let trans = CATransition()
            trans.type = "rippleEffect"
            trans.duration = 0.6
            button.layer.add(trans, forKey: nil)

            switch title {
            case "Animate":
                //perform animation
                button.setTitle("Stop And Finish", for: .normal)
                rotateAnimationRepeat()
                break
            default:
                //stop and finish
                button.setTitle("Animate", for: .normal)
                stopAnimationAndFinish()
                break
            }
        }

    }

    func rotateAnimationRepeat(){
        //just to be sure because of how i did the project
        animationView.layer.removeAllAnimations()
        let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
        rotation.fromValue = 0
        rotation.toValue =  Double.pi * 2
        rotation.duration = 0.5
        rotation.repeatCount = Float.greatestFiniteMagnitude
        //not doing cumlative
        animationView.layer.add(rotation, forKey: "rotationAnimation")
    }

    func stopAnimationAndFinish(){
        if let presentation = animationView.layer.presentation(){
            if let currentRotation = presentation.value(forKeyPath: "transform.rotation.z") as? CGFloat{

                var duration = 0.5

                //smooth out duration for change
                duration = Double((CGFloat(Double.pi * 2) - currentRotation))/(Double.pi * 2)
                animationView.layer.removeAllAnimations()

                let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
                rotation.fromValue = currentRotation
                rotation.toValue = Double.pi * 2
                rotation.duration = duration * 0.5
                animationView.layer.add(rotation, forKey: "rotationAnimation")

            }
        }
    }
}

结果:结果


1

2019典型的现代语法

将弧和层设置如下:

import Foundation
import UIKit

class RoundChaser: UIView {
    
    private let lineThick: CGFloat = 10.0
    private let beginFraction: CGFloat = 0.15
    // where does the arc drawing begin?
    // 0==top, .25==right, .5==bottom, .75==left
    
    private lazy var arcPath: CGPath = {
        let b = beginFraction * .pi * 2.0
        return UIBezierPath(
            arcCenter: bounds.centerOfCGRect(),
            radius: bounds.width / 2.0 - lineThick / 2.0,
            startAngle: .pi * -0.5 + b,
            // recall that .pi * -0.5 is the "top"
            endAngle: .pi * 1.5 + b,
            clockwise: true
        ).cgPath
    }()
    
    private lazy var arcLayer: CAShapeLayer = {
        let l = CAShapeLayer()
        l.path = arcPath
        l.fillColor = UIColor.clear.cgColor
        l.strokeColor = UIColor.purple.cgColor
        l.lineWidth = lineThick
        l.lineCap = CAShapeLayerLineCap.round
        l.strokeStart = 0
        l.strokeEnd = 0
        // if both are same, it is hidden. initially hidden
        layer.addSublayer(l)
        return l
    }()
    

然后初始化就很容易了。
    open override func layoutSubviews() {
        super.layoutSubviews()
        arcLayer.frame = bounds
    }
    

最终动画很容易

    public func begin() {
        CATransaction.begin()
        
        let e : CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd")
        e.duration = 2.0
        e.fromValue = 0
        e.toValue = 1.0
        // recall 0 will be our beginFraction, see above
        e.repeatCount = .greatestFiniteMagnitude
        
        self.arcLayer.add(e, forKey: nil)
        CATransaction.commit()
    }
}

0

也许这不是最好的解决方案,但它可以工作。正如你所说,一旦创建了CABasicAnimation,就无法修改其属性。此外,如果不删除rotation.repeatCount = Float.greatestFiniteMagnitude, if notCAAnimationDelegatemethodanimationDidStop`将永远不会被调用。采用这种方法,动画可以在需要时停止而不会出现任何问题。

步骤1:首先,在您的自定义类中声明一个变量标记,以便您需要停止动画。

var needStop : Bool = false

步骤 2:在结束后添加一个停止动画的方法

func stopAnimation()
{
    self.needStop = true
}

步骤3:添加一个获取自定义动画的方法

func getRotate360Animation() ->CAAnimation{
    let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
    rotation.fromValue = 0
    rotation.toValue =  Double.pi * 2
    rotation.duration = 1
    rotation.isCumulative = true
    rotation.isRemovedOnCompletion = false
    return rotation
}

步骤4:修改您的startRotate360函数,使用您的getRotate360Animation()方法

func startRotate360() {
    let rotationAnimation = self.getRotate360Animation()
    rotationAnimation.delegate = self
    self.layer.add(rotationAnimation, forKey: "rotationAnimation")
}

步骤 5: 在你的类中实现 CAAnimationDelegate

extension YOURCLASS : CAAnimationDelegate
{

    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        if(anim == self.layer?.animation(forKey: "rotationAnimation"))
        {
            self.layer?.removeAnimation(forKey: "rotationAnimation")
            if(!self.needStop){
                let animation = self.getRotate360Animation()
                animation.delegate = self
                self.layer?.add(animation, forKey: "rotationAnimation")
            }
        }
    }
}

这个代码可行并已经测试过了

希望这能对你有所帮助


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接