在SwiftUI中暂停和恢复动画非常容易,只需在withAnimation
块中确定动画类型即可。
你需要的是两个额外的信息:
- 动画暂停时的值是多少?
- 恢复时动画应该前往的值是多少?
这两个信息非常重要,因为请记住,SwiftUI视图只是表达您想要UI看起来像什么的方式,它们只是声明。除非您自己提供更新机制,否则它们不会保留UI的当前状态(例如当前旋转角度)。
虽然您可以引入计时器并在X毫秒的离散步骤中自行计算角度值,但没有必要。我建议让系统以其认为合适的方式计算角度值。
您唯一需要的是被通知该值,以便您可以存储它并用于设置正确的角度以进行暂停,并计算正确的目标角度以进行恢复。有几种方法可以做到这一点,我真的鼓励您阅读SwiftUI动画的三部分介绍,网址为https://swiftui-lab.com/swiftui-animations-part1/。
您可以采取的一种方法是使用GeometryEffect
。它允许您指定变换,旋转是基本变换之一,因此非常适合。它还遵循Animatable
协议,因此我们可以轻松地参与SwiftUI的动画系统。
重要的部分是让视图知道当前的旋转状态,以便它知道我们在暂停时应该保持什么角度,并且在恢复时应该前往什么角度。这可以通过一个简单的绑定来完成,该绑定仅用于将中间值从GeometryEffect
报告给视图。
下面是演示工作解决方案的示例代码:
import SwiftUI
struct PausableRotation: GeometryEffect {
@Binding var currentAngle: CGFloat
private var currentAngleValue: CGFloat = 0.0
var animatableData: CGFloat {
get { currentAngleValue }
set { currentAngleValue = newValue }
}
init(desiredAngle: CGFloat, currentAngle: Binding<CGFloat>) {
self.currentAngleValue = desiredAngle
self._currentAngle = currentAngle
}
func effectValue(size: CGSize) -> ProjectionTransform {
DispatchQueue.main.async {
self.currentAngle = currentAngleValue
}
let xOffset = size.width / 2
let yOffset = size.height / 2
let transform = CGAffineTransform(translationX: xOffset, y: yOffset)
.rotated(by: currentAngleValue)
.translatedBy(x: -xOffset, y: -yOffset)
return ProjectionTransform(transform)
}
}
struct DemoView: View {
@State private var isRotating: Bool = false
@State private var desiredAngle: CGFloat = 0.0
@State private var currentAngle: CGFloat = 0.0
var foreverAnimation: Animation {
Animation.linear(duration: 1.8)
.repeatForever(autoreverses: false)
}
var body: some View {
Button(action: {
self.isRotating.toggle()
let startAngle = currentAngle.truncatingRemainder(dividingBy: CGFloat.pi * 2)
let angleDelta = isRotating ? CGFloat.pi * 2 : 0.0
withAnimation(isRotating ? foreverAnimation : .linear(duration: 0)) {
self.desiredAngle = startAngle + angleDelta
}
}, label: {
Text("")
.font(.largeTitle)
.modifier(PausableRotation(desiredAngle: desiredAngle, currentAngle: $currentAngle))
})
}
}