'Press and hold' 没有 NSTimer 的动画
'Press and hold' animation without NSTimer
更新: NSTimer 方法现在可以使用,但会带来巨大的性能损失。这个问题现在缩小到 without NSTimers.
的方法
我正在尝试制作 'Press and hold' 交互式动画。在遵循大量 SO 答案之后,我主要遵循 @david-rönnqvist 控制动画计时中的方法。如果我使用 Slider 传递 layer.timeOffset
.
,它会起作用
但是,我似乎找不到一种在按住手势时连续更新相同动画的好方法。动画要么不开始,只显示开始帧,要么在某些点结束并拒绝再次开始。
任何人都可以帮助实现以下效果,没有我目前正在试验的可怕的 NSTimer 方法?
- 用户按下时,动画开始,圆圈填满。
- 当用户按住(不一定要移动手指)时,动画应该持续到最后并停留在该帧上。
- 当用户抬起手指时,动画应该反转,因此圆圈再次变空。
- 如果用户在动画过程中抬起手指或在反向过程中再次按下手指,动画应做出相应响应并从当前帧填充或清空。
这是我目前的努力 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
但是,在 beginAnimation
和 reverseAnimation
函数中,您使用的是 self.layer
。
尝试在您的计时器块中将 self.layer.timeOffset
替换为 self.fillShapeLayer!.timeOffset
。
解决方法有两个;
确保动画不会在完成时自行删除并保留其最后一帧。使用以下代码行轻松完成;
animation.fillMode = kCAFillModeForwards
animation.removedOnCompletion = false
困难的部分;您必须删除原始动画并开始一个从正确点开始的新的反向动画。这样做,给我以下代码;
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 解释了如何制作适当的暂停和恢复动画,该动画被转换为与反向动画一起使用。
更新: NSTimer 方法现在可以使用,但会带来巨大的性能损失。这个问题现在缩小到 without NSTimers.
的方法我正在尝试制作 'Press and hold' 交互式动画。在遵循大量 SO 答案之后,我主要遵循 @david-rönnqvist 控制动画计时中的方法。如果我使用 Slider 传递 layer.timeOffset
.
但是,我似乎找不到一种在按住手势时连续更新相同动画的好方法。动画要么不开始,只显示开始帧,要么在某些点结束并拒绝再次开始。
任何人都可以帮助实现以下效果,没有我目前正在试验的可怕的 NSTimer 方法?
- 用户按下时,动画开始,圆圈填满。
- 当用户按住(不一定要移动手指)时,动画应该持续到最后并停留在该帧上。
- 当用户抬起手指时,动画应该反转,因此圆圈再次变空。
- 如果用户在动画过程中抬起手指或在反向过程中再次按下手指,动画应做出相应响应并从当前帧填充或清空。
这是我目前的努力 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
但是,在 beginAnimation
和 reverseAnimation
函数中,您使用的是 self.layer
。
尝试在您的计时器块中将 self.layer.timeOffset
替换为 self.fillShapeLayer!.timeOffset
。
解决方法有两个;
确保动画不会在完成时自行删除并保留其最后一帧。使用以下代码行轻松完成;
animation.fillMode = kCAFillModeForwards animation.removedOnCompletion = false
困难的部分;您必须删除原始动画并开始一个从正确点开始的新的反向动画。这样做,给我以下代码;
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 解释了如何制作适当的暂停和恢复动画,该动画被转换为与反向动画一起使用。