前言
从一开始,动画就是 SwiftUI 最强大的功能之一。你可以在 SwiftUI 中快速构建流畅的动画。唯一的缺点是每当我们需要运行多步动画或将动画范围限定到视图层次结构的特定部分时,我们如何控制动画。
简单示例
让我们从一个简单的示例开始,展示我们旧方法的一些缺点,这些方法用于在 SwiftUI 中驱动动画。
代码语言:swift复制struct ContentView: View {
@State private var isHidden = false
var body: some View {
VStack {
Button("Animate") {
isHidden.toggle()
}
HugeView()
.opacity(isHidden ? 0.0 : 1.0)
AnotherHugeView()
}
.animation(.default)
}
}
如上例所示,我们有一个包含按钮和两个视图的视图层次结构,这些视图放置在垂直堆栈中。我们将动画视图修饰符附加到整个堆栈,以动画堆栈内的任何更改。
当我们按下按钮时,堆栈会动画显示内部的任何更改。但是,动画视图修饰符不连接到 isHidden 属性,这意味着它将动画显示可能发生的任何更改。其中一些更改可能是意外的,比如环境值的变化。
动画视图修饰符
我们可以通过使用动画视图修饰符的另一个版本来消除意外动画,在这个版本中,我们可以绑定到特定值,并且仅在值更改时进行动画处理。
代码语言:swift复制struct ContentView: View {
@State private var isHidden = false
var body: some View {
VStack {
Button("Animate") {
isHidden.toggle()
}
HugeView()
.opacity(isHidden ? 0.0 : 1.0)
AnotherHugeView()
}
.animation(.default, value: isHidden)
}
}
在上面的示例中,我们使用了带有 value 参数的动画视图修饰符。它允许我们将动画范围限定为单个值,并仅在与特定值相关的更改时执行动画。在这种情况下,我们没有任何意外的动画。
使用多个可动画属性
如果我们有多个可动画属性怎么办?
在这种情况下,我们必须为每个可动画属性附加一个动画修饰符。这个解决方案非常有效,但在人体工程学方面有一个缺点。
代码语言:swift复制struct ContentView: View {
@State private var firstStep = false
@State private var secondStep = false
var body: some View {
VStack {
Button("Animate") {
Task {
firstStep.toggle()
try? await Task.sleep(nanoseconds: 3_000_000_000)
secondStep.toggle()
}
}
// 其他视图在这里
SomeView()
.opacity(firstStep ? 1.0 : 0.0)
.blur(radius: secondStep ? 0 : 20.0)
}
.animation(.default, value: firstStep)
.animation(.default, value: secondStep)
}
}
幸运的是,SwiftUI 引入了动画视图修饰符的一个新变体,允许我们使用 ViewBuilder 闭包来限定动画的范围。
代码语言:swift复制struct ContentView: View {
@State private var firstStep = false
@State private var secondStep = false
var body: some View {
VStack {
Button("Animate") {
Task {
firstStep.toggle()
try? await Task.sleep(nanoseconds: 1_000_000_000)
secondStep.toggle()
}
}
// 其他视图在这里
SomeView()
.animation(.default) { content in
content
.opacity(firstStep ? 1.0 : 0.0)
.blur(radius: secondStep ? 0 : 20.0)
}
}
}
}
如上例所示,我们使用动画视图修饰符,提供我们需要的动画类型和一个 ViewBuilder 闭包,在这个动画中应用。动画仅在提供的 ViewBuilder 闭包的上下文中工作,不会扩展到其他任何地方。
使用 ViewBuilder
作为起点,ViewBuilder 闭包提供一个参数,用于占位视图,在其中应用了动画视图修饰符。在 ViewBuilder 闭包内部,可以安全地对视图应用任何视图修饰符,并期望仅对此代码块进行动画处理。
代码语言:swift复制struct ContentView: View {
@State private var firstStep = false
@State private var secondStep = false
var body: some View {
VStack {
Button("Animate") {
Task {
firstStep.toggle()
try? await Task.sleep(nanoseconds: 1_000_000_000)
secondStep.toggle()
}
}
// 其他视图在这里
SomeView()
.transaction { t in
t.animation = t.animation?.speed(2)
} body: { content in
content
.opacity(firstStep ? 1.0 : 0.0)
.blur(radius: secondStep ? 0 : 20.0)
}
}
}
}
正如你所看到的,SwiftUI 提供了一种类似的方法,以在视图层次结构中维护有作用域的事务。
总结
这篇文章介绍了在SwiftUI中构建动画的新方法,重点解决了在多步动画或特定视图层次结构中控制动画的挑战。通过引入带有value
参数的动画修饰符,以及使用ViewBuilder
闭包限定动画范围,作者展示了更精确和灵活的动画控制方式。
这种方法在处理多个可动画属性时尤其强大。文章还提到了SwiftUI引入的一项新变体,使用ViewBuilder
闭包可在动画中应用视图修饰符,有效地将动画范围限定在特定的上下文中。
最后,介绍了在 SwiftUI 中构建有作用域的事务的新方法,以维护更具精确性和可控性的动画。这些新功能在最新的平台上可用,为SwiftUI开发者提供了更强大的动画工具。