Animatable 协议:让 SwiftUI 动画不再“失控”
在 SwiftUI 中,Animatable
协议用于自定义视图或形状的动画插值过程。当系统默认的动画行为无法满足需求时,可以通过实现该协议,精细控制属性的过渡效果。以下是详细的使用场景和实现方法:
一、使用场景 #
自定义形状(Shape)的动画
当形状的属性变化需要平滑过渡(如绘制进度、顶点位置)时,Animatable
可以驱动路径的变化。多个属性的组合动画
若视图依赖多个参数的同步变化(如缩放和旋转),可用AnimatablePair
组合这些属性。非标准动画类型
当属性类型无法直接动画(如自定义枚举或结构体),需将其转换为可插值的数值(如CGFloat
)。覆盖默认动画行为
当系统默认插值逻辑不符合预期时,自定义animatableData
的插值方式。
二、使用方法 #
1. 基本实现 #
对于遵循 Shape
或 View
的类型,通过实现 animatableData
属性定义动画依赖的数据。
struct ProgressRing: Shape {
var progress: CGFloat
// 将 progress 映射为可动画数据
var animatableData: CGFloat {
get { progress }
set { progress = newValue }
}
func path(in rect: CGRect) -> Path {
Path { path in
let angle = 2 * .pi * progress
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY),
radius: rect.width / 2,
startAngle: .zero,
endAngle: Angle(radians: angle),
clockwise: false)
}
}
}
// 使用示例
struct ContentView: View {
@State private var progress: CGFloat = 0.0
var body: some View {
ProgressRing(progress: progress)
.stroke(Color.blue, lineWidth: 4)
.onTapGesture {
withAnimation(.easeInOut(duration: 1)) {
progress = progress > 0.5 ? 0 : 1
}
}
}
}
2. 组合多个属性(AnimatablePair) #
当需要同时动画多个属性时,使用 AnimatablePair
包装它们。
struct ScaledShape: Shape {
var scaleX: CGFloat
var scaleY: CGFloat
var animatableData: AnimatablePair<CGFloat, CGFloat> {
get { AnimatablePair(scaleX, scaleY) }
set {
scaleX = newValue.first
scaleY = newValue.second
}
}
func path(in rect: CGRect) -> Path {
let scaledRect = rect.applying(CGAffineTransform(scaleX: scaleX, y: scaleY))
return Rectangle().path(in: scaledRect)
}
}
// 使用示例
struct ContentView: View {
@State private var scaleX: CGFloat = 1.0
@State private var scaleY: CGFloat = 1.0
var body: some View {
ScaledShape(scaleX: scaleX, scaleY: scaleY)
.fill(Color.red)
.onTapGesture {
withAnimation(.spring()) {
scaleX = 2.0
scaleY = 0.5
}
}
}
}
3. 复杂类型动画(如角度转换) #
将非数值类型(如 Angle
)转换为可插值的数值。
struct RotatingTriangle: Shape {
var angle: Angle
var animatableData: Double {
get { angle.radians }
set { angle = Angle(radians: newValue) }
}
func path(in rect: CGRect) -> Path {
let center = CGPoint(x: rect.midX, y: rect.midY)
var path = Path()
// 根据 angle 绘制三角形路径
return path
}
}
三、关键点 #
遵循 VectorArithmetic 的类型
animatableData
必须为VectorArithmetic
类型(如CGFloat
、Double
、AnimatablePair
等)。性能优化
避免在animatableData
的get
/set
中执行复杂计算,确保插值高效。与动画修饰符配合
通过withAnimation
或.animation(_:)
触发动画,确保属性变化被系统捕获。
四、总结 #
通过 Animatable
协议,开发者可以:
- 为自定义视图或形状添加复杂的动画逻辑。
- 控制多个属性的同步插值。
- 将非标准类型转换为可动画数据。
正确使用该协议能显著增强 SwiftUI 动画的灵活性和表现力。