'Press and hold' 没有 NSTimer 的动画

'Press and hold' animation without NSTimer

更新: NSTimer 方法现在可以使用,但会带来巨大的性能损失。这个问题现在缩小到 without NSTimers.

的方法

我正在尝试制作 'Press and hold' 交互式动画。在遵循大量 SO 答案之后,我主要遵循 @david-rönnqvist 控制动画计时中的方法。如果我使用 Slider 传递 layer.timeOffset.

,它会起作用

但是,我似乎找不到一种在按住手势时连续更新相同动画的好方法。动画要么不开始,只显示开始帧,要么在某些点结束并拒绝再次开始。

任何人都可以帮助实现以下效果,没有我目前正在试验的可怕的 NSTimer 方法?

  1. 用户按下时,动画开始,圆圈填满。
  2. 当用户按住(不一定要移动手指)时,动画应该持续到最后并停留在该帧上。
  3. 当用户抬起手指时,动画应该反转,因此圆圈再次变空。
  4. 如果用户在动画过程中抬起手指或在反向过程中再次按下手指,动画应做出相应响应并从当前帧填充或清空。

这是我目前的努力 Github repo

如前所述,以下代码运行良好。它由滑块触发,效果很好。

func animationTimeOffsetToPercentage(percentage: Double) {
    if fillShapeLayer == nil {
        fillShapeLayer = constructFillShapeLayer()
    }

    guard let fillAnimationLayer = fillShapeLayer, let _ = fillAnimationLayer.animationForKey("animation") else {
        print("Animation not found")
        return
    }

    let timeOffset = maximumDuration * percentage
    print("Set animation to percentage \(percentage) with timeOffset: \(timeOffset)")

    fillAnimationLayer.timeOffset = timeOffset
}

然而,以下使用 NSTimers 的方法是可行的,但会产生令人难以置信的性能损失。我正在寻找一种不使用 NSTimer 的方法。

func beginAnimation() {
    if fillShapeLayer == nil {
        fillShapeLayer = constructFillShapeLayer()
    }

    animationTimer?.invalidate()
    animationTimer = NSTimer.schedule(interval: 0.1, repeats: true, block: { [unowned self] () -> Void in
        if self.layer.timeOffset >= 1.0 {
            self.layer.timeOffset = self.maximumDuration
        }
        else {
            self.layer.timeOffset += 0.1
        }
    })
}

func reverseAnimation() {
    guard let fillAnimationLayer = fillShapeLayer, let _ = fillAnimationLayer.animationForKey("animation") else {
        print("Animation not found")
        return
    }

    animationTimer?.invalidate()
    animationTimer = NSTimer.schedule(interval: 0.1, repeats: true, block: { [unowned self] () -> Void in
        if self.layer.timeOffset <= 0.0 {
            self.layer.timeOffset = 0.0
        }
        else {
            self.layer.timeOffset -= 0.1
        }
    })
}

当您使用滑块时,您使用 fillAnimationLayer 动画层

fillAnimationLayer.timeOffset = timeOffset 

但是,在 beginAnimationreverseAnimation 函数中,您使用的是 self.layer

尝试在您的计时器块中将 self.layer.timeOffset 替换为 self.fillShapeLayer!.timeOffset

解决方法有两个;

  1. 确保动画不会在完成时自行删除并保留其最后一帧。使用以下代码行轻松完成;

    animation.fillMode = kCAFillModeForwards
    animation.removedOnCompletion = false
    
  2. 困难的部分;您必须删除原始动画并开始一个从正确点开始的新的反向动画。这样做,给我以下代码;

    func setAnimation(layer: CAShapeLayer, startPath: AnyObject, endPath: AnyObject, duration: Double)
    {
        // Always create a new animation.
        let animation: CABasicAnimation = CABasicAnimation(keyPath: "path")
    
        if let currentAnimation = layer.animationForKey("animation") as? CABasicAnimation {
            // If an animation exists, reverse it.
            animation.fromValue = currentAnimation.toValue
            animation.toValue = currentAnimation.fromValue
    
            let pauseTime = layer.convertTime(CACurrentMediaTime(), fromLayer: nil)
            // For the timeSinceStart, we take the minimum from the duration or the time passed. 
            // If not, holding the animation longer than its duration would cause a delay in the reverse animation.
            let timeSinceStart = min(pauseTime - startTime, currentAnimation.duration)
    
            // Now convert for the reverse animation.
            let reversePauseTime = currentAnimation.duration - timeSinceStart
            animation.beginTime = pauseTime - reversePauseTime
    
            // Remove the old animation
            layer.removeAnimationForKey("animation")
            // Reset startTime, to be when the reverse WOULD HAVE started.
            startTime = animation.beginTime
        }
        else {
            // This happens when there is no current animation happening.
            startTime = layer.convertTime(CACurrentMediaTime(), fromLayer: nil)
    
            animation.fromValue = startPath
            animation.toValue = endPath
        }
    
        animation.duration = duration
        animation.fillMode = kCAFillModeForwards
        animation.removedOnCompletion = false
    
        layer.addAnimation(animation, forKey: "animation")
    }
    

Apple article 解释了如何制作适当的暂停和恢复动画,该动画被转换为与反向动画一起使用。