'按住'没有NSTimer的动画

时间:2016-01-27 12:06:34

标签: ios uigesturerecognizer caanimation

更新:NSTimer方法现在可以使用,但性能却很高。问题现在缩小到没有 NSTimers的方法。

我正在尝试动画“按住”互动动画。在跟踪了大量的SO答案之后,我大部分都遵循@ david-rönnqvist控制动画时间的方法。如果我使用Slider传递layer.timeOffset

,它就可以工作

但是,我似乎无法找到一种在按住手势时不断更新相同动画的好方法。动画要么没有开始,只显示开始帧,要么在某些时候结束并拒绝重新开始。

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

  1. 在用户按下时,动画开始,圆圈填满。
  2. 当用户持有(不一定要移动手指)时,动画应该一直持续到最后并保持在该帧上。
  3. 当用户抬起手指时,动画应该反转,这样圆圈就会再次清空。
  4. 如果用户在动画期间抬起手指或在反向过程中再次按下,则动画应相应地做出响应并从当前帧中填充或清空。
  5. The animation I want to achieve, but by pressing and holding

    这是我当前努力的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
            }
        })
    }
    

2 个答案:

答案 0 :(得分:1)

使用滑块时,使用fillAnimationLayer图层进行动画

fillAnimationLayer.timeOffset = timeOffset 

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

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

答案 1 :(得分:1)

解决方案是双重的;

  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")
    }
    
  3. 这个Apple article解释了如何进行适当的暂停和恢复动画,并将其转换为与反向动画一起使用。