TL;DR: SwiftUI 提供隐式动画(通过
.animation
声明)和显式动画(通过withAnimation
或withTransaction
定义)两种机制。隐式动画适合细化的效果控制,显式动画用于集中管理状态变化的动画需求。
背景
SwiftUI 提供了隐式动画和显式动画两种机制。许多开发者会困惑于它们的区别及适用场景。本文将通过示例对两者进行比较,并分析它们的适用场景与注意事项。
隐式动画
隐式动画是通过修饰器(如 .animation
,.transaction
)声明在状态变化时应应用的动画效果。它能沿视图树向下传递,并且可以被覆盖。
Swift
struct ImplicitAnimationDemo: View {
@State private var isActive = false
var body: some View {
VStack {
VStack {
Text("Hello")
.font(.largeTitle)
.offset(x: isActive ? 200 : 0)
// isActive 变化时, Text("Hello") 的 offset 应用 .smooth 动画
.animation(.smooth, value: isActive)
Text("World")
.font(.largeTitle)
.offset(x: isActive ? 200 : 0)
}
// isActive 变化时,VStack(包含其中的元素)应用 .linear
.animation(.linear.speed(3), value: isActive)
// 没有设置动画
Text("No Animation")
.offset(x: isActive ? 200 : 0)
Toggle("Active", isOn: $isActive)
.padding()
}
}
}
执行上面的代码,由于没有给 “No Animation” 设置动画,因此它在平移时没有动画。“World” 将使用在 VStack
上设置的动画 .linear.speed(3)
,“Hello” 则使用动画 .smooth
显式动画
显式动画使用命令式方法,通过withAnimation
或 withTransaction
显式定义动画范围。它适用于需要对状态变化动画进行集中控制的场景。
Swift
struct ExplicitAnimationDemo: View {
@State private var isActive = false
var body: some View {
VStack {
VStack {
Text("Hello")
.font(.largeTitle)
.offset(x: isActive ? 200 : 0)
// isActive 变化时, Text("Hello") 的 offset 应用 .smooth 动画
.animation(.smooth, value: isActive)
Text("World")
.font(.largeTitle)
.offset(x: isActive ? 200 : 0)
}
// isActive 变化时,VStack(包含其中的元素)应用 .linear
.animation(.linear.speed(0.5), value: isActive)
// 没有设置隐式动画
Text("No Animation")
.offset(x: isActive ? 200 : 0)
Button("Toggle") {
withAnimation(.spring){
isActive.toggle()
}
}
.buttonStyle(.borderedProminent)
}
}
}
执行上面的代码,“No Animation” 由于同样受 isActive
状态变化(包含在显式动画闭包中)的影响,而且没有被声明隐式动画,此时它将使用 withAnimation
提供的动画设定:.spring
。
两者对比
- 互补关系:隐式动画和显式动画是互补的机制,分别适用于不同的动画需求场景。
- 精细调整与集中控制:隐式动画通过对具体元素的声明,实现对动画细节的精准控制;显式动画则更适合需要集中管理状态变化动画的场景。
- 默认优先级:显式动画可为未设置隐式动画的视图提供默认动画效果,保证一致性。
- 动画屏蔽规则:当显式动画通过
disablesAnimations
设置为true
时,隐式动画将被屏蔽而失效。
Swift
var transaction = Transaction()
transaction.disablesAnimations = true
withTransaction(transaction){
isActive.toggle()
}
隐式动画的注意事项
随着 SwiftUI 的不断更新,新增了多种隐式动画声明方式。在可能的情况下,建议优先使用新版本的隐式动画设置,以便为特定元素提供更精确的动画控制和指引。
.animation(.spring)
对当前分支的所有状态变化应用.spring
动画,无论具体状态为何。从 iOS 14 后废弃。.animation(.spring, value: isActive)
仅在isActive
状态发生变化时,对当前分支应用.spring
动画。.animation(animation:, body:)
仅为特定元素声明动画,而非影响整个分支。