SwiftUI 动画通过在视图的两个状态之间进行插值来工作:你修改某个状态,SwiftUI就将依赖该状态的属性从旧值逐渐变化到新值。你可以决定什么时候播放动画——通过.animation(_:value)隐式指定,或者通过withAnimation显式指定;也可以决定动画如何变化——使用弹簧、缓动曲线或关键帧时间线。什么叫插值假设矩形宽度从120变到280,没有动画时,界面直接显示280;有动画时,中间会显示一系列过度宽度。你负责提供目标状态,SwiftUI 负责计算和呈现中间过程。“所有依赖该状态的属性”的意思为:只有支持动画、并且处于有效动画事务中的变化,才能按对应方式产生动画。布尔值本身不会逐渐从false变成true,它决定的宽度、透明度等属性才会插值。另外,状态值通常已经变成目标值,屏幕上的视觉效果仍在过度。不要把“动画进行到一半”理解成“状态也只修改到一半”。隐式动画与显式动画隐式动画附加在视图上,每当某个指定值发生变化时触发。SWIFTstruct ImplicitExample: View { @State private var isExpanded = false var body: some View { RoundedRectangle(cornerRadius: 20) .fill(.orange) .frame(width: isExpanded ? 280 : 120, height: 120) .animation(.spring(duration: 0.4, bounce: 0.2), value: isExpanded) .onTapGesture { isExpanded.toggle() } } }始终传入value:。旧版不带value的.animation(_:)会为传递到该视图的各种变化添加动画,包括那些你并不想动画化的变化;这个旧接口已经被弃用。显式动画则包裹状态修改本身。由这次修改引起的变化,无论位于视图层级的什么位置,都可以参与动画。SWIFTstruct ExplicitExample: View { @State private var isExpanded = false var body: some View { VStack(spacing: 16) { RoundedRectangle(cornerRadius: 20) .fill(.orange) .frame(width: isExpanded ? 200: 120, height: 120) Button(isExpanded ? "Collapse" : "Expand") { withAnimation { isExpanded.toggle() } } } } }一个实用的经验是:用户操作,例如点击、提交、选择,使用withAnimation;自行发生的值变化,例如模型更新、定时器触发,使用隐式动画。在 iOS 中, withAnimation(_:completionCriteria:_:completion:)还提供完成回调,可以代替大部分通过DispatchQueue.main.asyncAfter 猜测动画完成时间的临时做法两者真正的区别是“动画规则放在哪里”第一个例子把规则放在视图上:“当isExpanded改变时,这里用弹簧动画更新。”修改状态的位置不需要知道动画细节。第二个例子把规则放在操作上:“这次切换状态时,使用.snappy动画。”同一个状态在其他地方修改时,可以采用另一种动画,也可以不播放动画。所以,“用户操作用显式、自动更新用隐式”只是作者的组织代码建议,不是 SwiftUI 的强制规则。两个例子都是点击触发,恰好也说明这一点。所以,“用户操作用显式、自动更新用隐式”不是 SwiftUI 的强制规则。两个例子都是点击触发,恰好也说明这点。value:是触发条件,不是属性白名单.animation(..., value: isExpanded)不代表“只有直接依赖isExpanded的属性才能动”。当它触发时,同一次更新中、其作用范围内的其他可动画属性变化也可能受影响。因此要同时关注触发值和修饰器的位置。同样,withAnimation也不保证所有相关变化必然动画化:子视图可以覆盖或禁用动画,不支持插值的变化也不会自动变的可插值。Apple将这套行为组织在动画和事务机制中。如果拿 UIKit 作类比,withAnimation有点像UIView.animate中提交目标变化;区别在于,SwiftUI 中你通常修改的事状态,再由状态生成目标界面。弹簧动画弹簧
SwiftUI 动画通过在视图的两个状态之间进行插值来工作:你修改某个状态,SwiftUI就将依赖该状态的属性从旧值逐渐变化到新值。你可以决定什么时候播放动画——通过.animation(_:value)隐式指定,或者通过withAnimation显式指定;也可以决定动画如何变化——使用弹簧、缓动曲线或关键帧时间线。
什么叫插值
假设矩形宽度从120变到280,没有动画时,界面直接显示280;有动画时,中间会显示一系列过度宽度。你负责提供目标状态,SwiftUI 负责计算和呈现中间过程。
“所有依赖该状态的属性”的意思为:只有支持动画、并且处于有效动画事务中的变化,才能按对应方式产生动画。布尔值本身不会逐渐从false变成true,它决定的宽度、透明度等属性才会插值。
另外,状态值通常已经变成目标值,屏幕上的视觉效果仍在过度。不要把“动画进行到一半”理解成“状态也只修改到一半”。
隐式动画附加在视图上,每当某个指定值发生变化时触发。
始终传入value:。旧版不带value的.animation(_:)会为传递到该视图的各种变化添加动画,包括那些你并不想动画化的变化;这个旧接口已经被弃用。
显式动画则包裹状态修改本身。由这次修改引起的变化,无论位于视图层级的什么位置,都可以参与动画。
一个实用的经验是:用户操作,例如点击、提交、选择,使用withAnimation;自行发生的值变化,例如模型更新、定时器触发,使用隐式动画。
在 iOS 中, withAnimation(_:completionCriteria:_:completion:)还提供完成回调,可以代替大部分通过DispatchQueue.main.asyncAfter 猜测动画完成时间的临时做法
两者真正的区别是“动画规则放在哪里”
第一个例子把规则放在视图上:“当isExpanded改变时,这里用弹簧动画更新。”修改状态的位置不需要知道动画细节。
第二个例子把规则放在操作上:“这次切换状态时,使用.snappy动画。”同一个状态在其他地方修改时,可以采用另一种动画,也可以不播放动画。
所以,“用户操作用显式、自动更新用隐式”只是作者的组织代码建议,不是 SwiftUI 的强制规则。两个例子都是点击触发,恰好也说明这一点。
所以,“用户操作用显式、自动更新用隐式”不是 SwiftUI 的强制规则。两个例子都是点击触发,恰好也说明这点。
value:是触发条件,不是属性白名单
.animation(..., value: isExpanded)不代表“只有直接依赖isExpanded的属性才能动”。当它触发时,同一次更新中、其作用范围内的其他可动画属性变化也可能受影响。因此要同时关注触发值和修饰器的位置。
同样,withAnimation也不保证所有相关变化必然动画化:子视图可以覆盖或禁用动画,不支持插值的变化也不会自动变的可插值。Apple将这套行为组织在动画和事务机制中。
如果拿 UIKit 作类比,withAnimation有点像UIView.animate中提交目标变化;区别在于,SwiftUI 中你通常修改的事状态,再由状态生成目标界面。
弹簧