【问题标题】:Animate drawing of full circle continuously without any lag during restart using CABasicAnimation使用 CABasicAnimation 在重新启动期间连续绘制完整圆的动画,没有任何延迟
【发布时间】:2017-10-05 08:56:06
【问题描述】:

我试图在视图控制器中制作动画,其中圆圈随着动画旋转。圆圈应该旋转,直到一个过程完成,如下面的 gif 所示。我已经实现了圆形动画,但无法达到我想要实现的目标。

import UIKit

class ViewController: UIViewController {

    var circle : Circle?;

    override func viewDidLoad() {
        super.viewDidLoad();

        view.backgroundColor = UIColor.white;

        setupViews();
    }

    func setupViews(){
        circle = Circle(frame: self.view.frame);

        view.addSubview(circle!);

        circle?.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true;
        circle?.topAnchor.constraint(equalTo: view.topAnchor).isActive = true;
        circle?.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true;
        circle?.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true;

    }

}

class Circle : UIView{


    override init(frame: CGRect) {
        super.init(frame: frame);

        self.backgroundColor = .blue;
        self.translatesAutoresizingMaskIntoConstraints = false;

        setupCircle();
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    let gradientLayer = CAGradientLayer();


    func setupCircle(){

        layer.addSublayer(shapeLayer);

        let circlePath = UIBezierPath(arcCenter: CGPoint(x: self.frame.width / 2 - 50, y: self.frame.height / 2 - 50), radius: 50, startAngle: CGFloat(Double.pi * (0 / 4)), endAngle: CGFloat(Double.pi * 2), clockwise: true);

        shapeLayer.path = circlePath.cgPath;

        let group = CAAnimationGroup()
        group.animations = [animateStrokeEnd, animateOpacity]
        group.duration = 0.8

        group.repeatCount = HUGE // repeat forver
        shapeLayer.add(group, forKey: nil)
    }

    let shapeLayer: CAShapeLayer = {
        let layer = CAShapeLayer();
        layer.strokeColor = UIColor.white.cgColor;
        layer.lineWidth = 5;
        layer.fillColor = UIColor.clear.cgColor;
        layer.strokeStart = 0
        layer.strokeEnd = 1;
        return layer;
    }();

    let animateOpacity : CABasicAnimation = {
        let animation = CABasicAnimation(keyPath: "opacity");
        animation.fromValue = 0;
        animation.toValue = 0.8;
        animation.byValue = 0.01;
        animation.repeatCount = Float.infinity;
        return animation
    }();

    let animateStrokeEnd: CABasicAnimation = {
        let animation = CABasicAnimation(keyPath: "strokeEnd");
        animation.fromValue  = 0;
        animation.repeatCount = Float.infinity;
        animation.toValue = 1;
        return animation;
    }();

}

我正在使用 strokeEnd 动画来实现动画。和不透明度为颜色设置动画。但是当圆圈达到 360 度时,它会在开始新的圆圈之前有所滞后。

有人知道如何消除这种效果并获得流畅的动画吗?

The above code produces this animation

But i want to achieve this animation

笔触颜色也与原始动画不同。我们可以使用 CABasicAnimation 来实现这个动画吗?

【问题讨论】:

    标签: swift animation


    【解决方案1】:

    与其尝试为实际绘图设置动画,不如绘制一次视图然后对其进行动画处理。

    这是一个自定义 PadlockView 和一个自定义 CircleView,它们模仿了您展示的动画。要使用它,请将以下代码添加到您的项目中。将UIView 添加到您的故事板,将其类更改为PadlockView,并为其创建一个@IBOutlet(可能称为padlock)。当您希望视图具有动画效果时,请设置padlock.circle.isAnimating = true。要停止动画,请设置padlock.circle.isAnimating = false。


    CircleView.swift

    // This UIView extension was borrowed from @keval's answer:
    // https://stackoverflow.com/a/41160100/1630618
    extension UIView {
        func rotate360Degrees(duration: CFTimeInterval = 3) {
            let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
            rotateAnimation.fromValue = 0.0
            rotateAnimation.toValue = CGFloat.pi * 2
            rotateAnimation.isRemovedOnCompletion = false
            rotateAnimation.duration = duration
            rotateAnimation.repeatCount = Float.infinity
            self.layer.add(rotateAnimation, forKey: nil)
        }
    }
    
    class CircleView: UIView {
    
        var foregroundColor = UIColor.white
        var lineWidth: CGFloat = 3.0
    
        var isAnimating = false {
            didSet {
                if isAnimating {
                    self.isHidden = false
                    self.rotate360Degrees(duration: 1.0)
                } else {
                    self.isHidden = true
                    self.layer.removeAllAnimations()
                }
            }
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            setup()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setup()
        }
    
        func setup() {
            self.isHidden = true
            self.backgroundColor = .clear
        }
    
        override func draw(_ rect: CGRect) {
            let width = bounds.width
            let height = bounds.height
            let radius = (min(width, height) - lineWidth) / 2.0
    
            var currentPoint = CGPoint(x: width / 2.0 + radius, y: height / 2.0)
            var priorAngle = CGFloat(360)
    
            for angle in stride(from: CGFloat(360), through: 0, by: -2) {
                let path = UIBezierPath()
                path.lineWidth = lineWidth
    
                path.move(to: currentPoint)
                currentPoint = CGPoint(x: width / 2.0 + cos(angle * .pi / 180.0) * radius, y: height / 2.0 + sin(angle * .pi / 180.0) * radius)
                path.addArc(withCenter: CGPoint(x: width / 2.0, y: height / 2.0), radius: radius, startAngle: priorAngle * .pi / 180.0 , endAngle: angle * .pi / 180.0, clockwise: false)
                priorAngle = angle
    
                foregroundColor.withAlphaComponent(angle/360.0).setStroke()
                path.stroke()
            }
        }
    
    }
    

    PadlockView.swift

    class PadlockView: UIView {
    
        var circle: CircleView!
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            setup()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setup()
        }
    
        func setup() {
            self.backgroundColor = .clear
    
            circle = CircleView()
            circle.translatesAutoresizingMaskIntoConstraints = false
            self.addSubview(circle)
            circle.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
            circle.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
            circle.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
            circle.heightAnchor.constraint(equalTo: self.heightAnchor).isActive = true
        }
    
        override func draw(_ rect: CGRect) {
            let width = bounds.width
            let height = bounds.height
    
            let lockwidth = width / 3
            let lockheight = height / 4
    
            let boltwidth = lockwidth * 2 / 3
    
            UIColor.white.setStroke()
    
            let path = UIBezierPath()
            path.move(to: CGPoint(x: (width - lockwidth) / 2, y: height / 2))
            path.addLine(to: CGPoint(x: (width + lockwidth) / 2, y: height / 2))
            path.addLine(to: CGPoint(x: (width + lockwidth) / 2, y: height / 2 + lockheight))
            path.addLine(to: CGPoint(x: (width - lockwidth) / 2, y: height / 2 + lockheight))
            path.close()
            path.move(to: CGPoint(x: (width - boltwidth) / 2, y: height / 2))
            path.addLine(to: CGPoint(x: (width - boltwidth) / 2, y: height / 2 - boltwidth / 4))
            path.addArc(withCenter: CGPoint(x: width/2, y: height / 2 - boltwidth / 4), radius: boltwidth / 2, startAngle: .pi, endAngle: 0, clockwise: true)
            path.lineWidth = 2.0
            path.stroke()
        }
    
    }
    

    注意:连续动画代码由this answer提供。


    这是我在ViewController 中使用以下代码设置的演示:

    @IBOutlet weak var padlock: PadlockView!
    
    @IBAction func startStop(_ sender: UIButton) {
        if sender.currentTitle == "Start" {
            sender.setTitle("Stop", for: .normal)
            padlock.circle.isAnimating = true
        } else {
            sender.setTitle("Start", for: .normal)
            padlock.circle.isAnimating = false
        }
    }
    

    【讨论】:

    • 感谢@vacawam 的努力。这就是我所需要的。你节省了我很多时间。
    • 一件事。如果我把挂锁放在圆圈的中心会发生什么。挂锁和圆圈都会旋转。有没有办法只为每一层设置动画。
    • 我用PadlockView 更新了答案。 PadlockView 添加一个 CircleView 作为它的子视图,并添加约束以使视图大小相同。您只需将PadlockView 添加到您的故事板。圆的动画是通过PadlockView 的circle 属性完成的。
    • 我为挂锁创建了另一个视图,它工作正常。但我遇到了一个问题。当我按下停止按钮时,我希望动画圈停止在当前位置。但是每次单击停止按钮时,此动画都会重置圆的位置。
    • 我们可以通过暂停旋转来停止动画圈。但我找不到任何暂停视图旋转的逻辑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多