如何在 CABasicAnimation 结束之前重置它?

Posted

技术标签:

【中文标题】如何在 CABasicAnimation 结束之前重置它?【英文标题】:How to reset a CABasicAnimation before it has ended? 【发布时间】:2018-10-17 03:44:17 【问题描述】:

我有一个 CAShapelayer 圆圈,我用 CABasicAnimation 从 0 到 360 度将其设置为进度圆圈,同时在状态机中使用具有重置、播放、暂停和完成状态的倒数计时器。

现在,我可以播放或暂停并继续播放,直到计时器结束并且动画完成并且模型恢复其原始值,然后选择另一个倒计时计时器值以开始动画。

但是,如果我播放然后在动画完成之前的任何时间重置,它会取消,但是当我再次播放时,动画不再起作用。我注意到由于某种原因在动画完成之前我重置后,我的 strokeEnd 不再从 0.0 开始,而是得到一个看似任意的小数点值。我认为这是我的问题的根本原因,但我不知道为什么 strokeEnd 值是这些随机数。这是 strokeEnd 值的屏幕截图 - https://imgur.com/a/YXmNkaK

这是我目前所拥有的:

//draws CAShapelayer 
func drawCircle() 

//CAShapeLayer animation
func progressCircleAnimation(transitionDuration: TimeInterval, speed: Double, strokeEnd: Double) 
    let fillLineAnimation = CABasicAnimation(keyPath: "strokeEnd")
    fillLineAnimation.duration = transitionDuration
    fillLineAnimation.fromValue = 0
    fillLineAnimation.toValue = 1.0
    fillLineAnimation.speed = Float(speed)
    circleWithProgressBorderLayer.strokeEnd = CGFloat(strokeEnd)
    circleWithProgressBorderLayer.add(fillLineAnimation, forKey: "lineFill")


//Mark: Pause animation 
func pauseLayer(layer : CALayer) 
    let pausedTime : CFTimeInterval = circleWithProgressBorderLayer.convertTime(CACurrentMediaTime(), from: nil)
    circleWithProgressBorderLayer.speed = 0.0
    circleWithProgressBorderLayer.timeOffset = pausedTime


//Mark: Resume CABasic Animation on CAShaperLayer at pause offset
func resumeLayer(layer : CALayer) 
    let pausedTime = circleWithProgressBorderLayer.timeOffset
    circleWithProgressBorderLayer.speed = 1.0;
    circleWithProgressBorderLayer.timeOffset = 0.0;
    circleWithProgressBorderLayer.beginTime = 0.0;
    let timeSincePause = circleWithProgressBorderLayer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
    circleWithProgressBorderLayer.beginTime = timeSincePause;


//Mark: Tried to removeAnimation
func resetLayer(layer : CALayer) 
    layer.removeAnimation(forKey: "lineFill")
    circleWithProgressBorderLayer.strokeEnd = 0.0

这是我在状态机中设置它的方式,以防错误与此有关:

@objc func timerIsReset() 
    resetTimer()
    let currentRow = timePickerView.selectedRow(inComponent: 0)
    self.counter = self.Times[currentRow].amount


//Mark: action for RunningTimerState
@objc func timerIsStarted() 
    runTimer()


//Mark: action for PausedTimerState
@objc func timerIsPaused() 

    pauseTimer()


//Mark: action for TimerTimeRunOutState
func timerTimeRunOut() 

    resetTimer()


func runTimer() 
    if isPaused == true 
        finishTime = Date().addingTimeInterval(-remainingTime)
        timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)

        resumeLayer(layer: circleWithProgressBorderLayer)

     else if isPaused == false 
        finishTime = Date().addingTimeInterval(counter)
        timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)

        progressCircleAnimation(transitionDuration: counter, speed: 1.0, strokeEnd: self.completionPercentage)
    


//Mark: Pause Timer and pause CABasic Animation
func pauseTimer() 
    timer.invalidate()
    isPaused = true
    remainingTime = -finishTime.timeIntervalSinceNow
    completionPercentage = (counter + remainingTime) / counter

    pauseLayer(layer: circleWithProgressBorderLayer)


func resetTimer() 
    timer.invalidate()
    isPaused = false
    resetLayer(layer: circleWithProgressBorderLayer)

【问题讨论】:

UIViewPropertyAnimator 用于动画视图属性。 CAShapeLayer 不是视图,也没有视图属性。所以使用 CABasicAnimation 是正确的。要为图层设置动画,请使用图层动画。 @matt 谢谢。有没有办法暂停和恢复 CABasicAnimation? 是的,当然。要暂停,只需将图层的speed 设置为0。这就是 UIViewPropertyAnimator 真正在幕后所做的一切。这不是魔术。 【参考方案1】:

假设您的动画看起来像这样......

func runTimerMaskAnimation(duration: CFTimeInterval, fromValue : Double)

       ...
             let path = UIBezierPath(roundedRect: circleBounds, cornerRadius: 
             circleBounds.size.width * 0.5)
             maskLayer?.path = path.reversing().cgPath
             maskLayer?.strokeEnd = 0
    
             parentCALayer.mask = maskLayer
    
             let animation = CABasicAnimation(keyPath: "strokeEnd")
             animation.duration = duration
             animation.fromValue = fromValue
             animation.toValue = 0.0
             maskLayer?.add(animation!, forKey: "strokeEnd")

如果我想在动画完成之前将计时器重新启动到原始位置,我会移除动画,移除 maskLayer,然后再次运行动画。

 maskLayer?.removeAnimation(forKey: "strokeEnd")
 maskLayer?.removeFromSuperlayer()
 runTimerMaskAnimation(duration: 15, fromValue : 1)

【讨论】:

以上是关于如何在 CABasicAnimation 结束之前重置它?的主要内容,如果未能解决你的问题,请参考以下文章

当我在animationDidStart中设置结束值时,CABasicAnimation没有插值:

在 CABasicAnimation 中闪烁以进行旋转

CALayer的动画结束回调?

基本动画CABasicAnimation - 完成之后闪回初始状态

CABasicAnimation 在它开始之前就停止了吗?

重新启动 CABasicAnimation