【问题标题】:Stop animation at a certain point and start up from that point again在某个点停止动画并从该点重新启动
【发布时间】:2020-07-01 09:33:51
【问题描述】:

背景

大家好!我正在为我的应用程序创建一个计时器功能。

此计时器的一部分包括一个围绕实际倒计时数字的进度环,随着计时器的进行而填充。

我在下面添加了图片,以防我的解释不容易理解。

我的目标是让绿色笔触在点击“暂停”按钮时停止动画,并在点击“继续”按钮时继续动画。

到目前为止,绿色笔划只有在按下开始按钮后才能正确动画。

为了提供有关我的代码如何工作的更多背景信息,我添加了以下内容。

为了制作圆,我创建了一个 CAShapeLayer 对象 (shapeLayer),其 .path 等于圆形 UIBezierPath .CGPath

let center = view.center
let circularPath = UIBezierPath(arcCenter: center, radius: view.bounds.width/2.5, startAngle: -CGFloat.pi/2, endAngle: 2*CGFloat.pi, clockwise: true)

shapeLayer.path = circularPath.cgPath
shapeLayer.strokeColor = UIColor.systemGreen.cgColor
shapeLayer.lineCap = .round
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 7
shapeLayer.strokeEnd = 0
view.layer.addSublayer(shapeLayer)

接下来,我使用 CABasicAnimaton 对象“basicAnimation”为外部笔划设置动画。我在下面详述的 doAnimate() 函数中完成所有动画。

func doAnimate(n: Int){
        if n == 1{ //if n is 1, start from zero
            basicAnimation.toValue = 0.80  // my circles start point is 0, and the endpoint at the top is .8 not 1
        }//end of if
        if n == 2{ //if n is 2, the timer was already started and the pause button has been pressed
            //stop the animation where it is at
        }//end of if
        if n == 3{//if n is 3, the continue button has been pressed
            // continue the animation from where it was stopped
        }//end of if

        basicAnimation.duration = CFTimeInterval(1500) //this is a set 25 minute timer which is why the duration is 1500
        basicAnimation.fillMode = .forwards
        
        basicAnimation.isRemovedOnCompletion = false
        shapeLayer.add(basicAnimation, forKey: "urSoBasic")
    }//end of func

问题

在我所有的 IBOUtlet 按钮函数中,我调用 doAnimate 函数并传入一个整数 n。

我的整数 'n' 的目的是决定笔划在进度条上的位置。

如果 n == 1,笔画将从 0 开始。

如果 n == 2,则表示用户按下了暂停按钮,我需要笔画停止并保持在原位。

如果 n == 3,那么用户按下了继续按钮,我需要笔画再次继续。

当我按下暂停按钮时,我的动画笔画无法暂停。

同样,当我按下继续按钮时,我无法让它继续。

我整天都在研究解决此问题的代码,但我找不到任何代码。

问题

我需要中风来停止动画并在按下暂停按钮后保持原位。

如果按下继续按钮,我需要笔画来继续制作动画。

如何停止动画,然后让它从该点恢复?

【问题讨论】:

    标签: swift animation


    【解决方案1】:

    你问:

    我需要中风来停止动画并在按下暂停按钮后保持原位。

    您可以使用presentation() 检索“表示层当前显示在屏幕上的状态的表示层对象的副本”,即层中间动画的状态。在删除动画之前使用它来设置strokeEnd

    if let presentationLayer = progressShapeLayer.presentation() {
        progressShapeLayer.strokeEnd = presentationLayer.strokeEnd
    }
    progressShapeLayer.removeAnimation(forKey: animationKey)
    

    如果按下继续按钮,我需要笔画来继续制作动画。

    当您重新启动动画时,将此 strokeEnd 值用于您的 fromValue


    例如:

    @IBDesignable
    public class CircularProgressView: UIView {
    
        @IBInspectable var duration:  CFTimeInterval = 10
        @IBInspectable var lineWidth: CGFloat = 1  { didSet { updatePaths() } }
    
        private(set)   var isRunning = false
        private        var elapsed: CFTimeInterval = 0
        private        var startTime: CFTimeInterval!
        private        let animationKey = Bundle.main.bundleIdentifier! + ".strokeEnd"
    
        private let backgroundShapeLayer: CAShapeLayer = {
            let shapeLayer = CAShapeLayer()
            shapeLayer.strokeColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1).cgColor
            shapeLayer.fillColor = UIColor.clear.cgColor
            return shapeLayer
        }()
        
        private let progressShapeLayer: CAShapeLayer = {
            let shapeLayer = CAShapeLayer()
            shapeLayer.strokeColor = #colorLiteral(red: 0.2319213939, green: 0.5, blue: 0.224438305, alpha: 1).cgColor
            shapeLayer.fillColor = UIColor.clear.cgColor
            shapeLayer.lineCap = .round
            shapeLayer.strokeEnd = 0
            return shapeLayer
        }()
        
        override init(frame: CGRect = .zero) {
            super.init(frame: frame)
            configure()
        }
        
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            configure()
        }
        
        public override func layoutSubviews() {
            super.layoutSubviews()
            updatePaths()
        }
        
        public override func prepareForInterfaceBuilder() {
            progressShapeLayer.strokeEnd = 1 / 3
        }
    }
    
    // MARK: - Public interface
    
    public extension CircularProgressView {
        func start(duration: CFTimeInterval) {
            self.duration = duration
            reset()
            resume()
        }
        
        func pause() {
            guard
                isRunning,
                let presentation = progressShapeLayer.presentation()
            else {
                return
            }
            
            elapsed += CACurrentMediaTime() - startTime
            progressShapeLayer.strokeEnd = presentation.strokeEnd
            progressShapeLayer.removeAnimation(forKey: animationKey)
        }
        
        func resume() {
            guard !isRunning else { return }
            
            isRunning = true
            startTime = CACurrentMediaTime()
            let animation = CABasicAnimation(keyPath: "strokeEnd")
            animation.fromValue = elapsed / duration
            animation.toValue = 1
            animation.duration = duration - elapsed
            animation.delegate = self
            progressShapeLayer.strokeEnd = 1
            progressShapeLayer.add(animation, forKey: animationKey)
        }
        
        func reset() {
            isRunning = false
            progressShapeLayer.removeAnimation(forKey: animationKey)
            progressShapeLayer.strokeEnd = 0
            elapsed = 0
        }
    }
    
    extension CircularProgressView: CAAnimationDelegate {
        public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
            isRunning = false
        }
    }
    
    // MARK: - Utility
    
    private extension CircularProgressView {
        func configure() {
            layer.addSublayer(backgroundShapeLayer)
            layer.addSublayer(progressShapeLayer)
        }
        
        func updatePaths() {
            let radius = (min(bounds.width, bounds.height) - lineWidth) / 2
            let center = CGPoint(x: bounds.midX, y: bounds.midY)
            let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: -.pi / 2, endAngle: 3 * .pi / 2, clockwise: true)
            
            backgroundShapeLayer.lineWidth = lineWidth
            progressShapeLayer.lineWidth = lineWidth
            
            backgroundShapeLayer.path = path.cgPath
            progressShapeLayer.path = path.cgPath
        }
    }
    

    产量:

    【讨论】:

    • Rob,presentationLayer 检索图层状态的副本,但动画仍在继续。如果我再次按下暂停按钮,它只会重置回检索到的图层。 removeAnimation 功能对我不起作用。我也尝试了 view.layer.removeAllAnimation(),但这也行不通。知道为什么这不起作用吗?
    • @Stryka - 查看示例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-03
    • 2012-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-01
    • 1970-01-01
    相关资源
    最近更新 更多