在 SwiftUI 中,AnimatableData 和 AnimatablePair 是动画的核心概念,特别是当需要对复杂的形状或自定义组件实现动画时。以下是它们的详细解释和 AnimatableArc 的实现说明:
1、AnimatableData
AnimatableData 是 Shape 和其他可动画视图的重要协议属性,用于描述该视图中可以参与动画的属性。
特点:
每个遵循 Shape 协议的自定义形状都需要实现 animatableData。
它通常存储形状的可动画参数,例如角度、位置、比例等。
默认实现:
对于简单数据类型(如 CGFloat 或 Double),animatableData 通常是这些类型本身。
对于多值情况(如两个角度),可以使用 AnimatablePair。
2、AnimatablePair
AnimatablePair 是一个 SwiftUI 提供的类型,用于同时存储和动画两个不同的值。
特点:
用于将多个动画数据值组合成一个。
可以嵌套,支持更复杂的动画场景(如三值组合 AnimatablePair<AnimatablePair<T, U>, V>)。
主要属性:
.first:第一个值。
.second:第二个值。
用法场景:
当一个形状需要两个或多个动画参数时(如起始角度和结束角度),使用 AnimatablePair 将它们组合起来。
AnimatableArc 示例
下面是一个完整的 AnimatableArc 示例,展示了如何使用 AnimatableData 和 AnimatablePair 实现动画。
实现:
import SwiftUI
struct AnimatableArc: Shape {
// 起始角度和结束角度
var startAngle: Angle
var endAngle: Angle
// 动画数据,使用 AnimatablePair 将两个角度组合
var animatableData: AnimatablePair<Double, Double> {
get { AnimatablePair(startAngle.radians, endAngle.radians) }
set {
startAngle = .radians(newValue.first) // 解包第一个值
endAngle = .radians(newValue.second) // 解包第二个值
}
}
// 绘制路径
func path(in rect: CGRect) -> Path {
var path = Path()
let center = CGPoint(x: rect.midX, y: rect.midY)
let radius = min(rect.width, rect.height) / 2
path.addArc(center: center,
radius: radius,
startAngle: startAngle,
endAngle: endAngle,
clockwise: false)
return path
}
}
逻辑代码解析
1、基本定义
struct AnimatableArc: Shape {
var startAngle: Angle
var endAngle: Angle
}
AnimatableArc:这是一个结构体,遵循了 SwiftUI 的 Shape 协议。Shape 是用于绘制形状(如矩形、圆形、自定义路径等)的协议。
startAngle 和 endAngle:定义了弧形的起始角度和结束角度。它们的类型是 Angle,SwiftUI 提供的一个类型,用于更直观地表示角度(可以使用 .degrees 或 .radians 创建)。
2、动画数据支持
var animatableData: AnimatablePair<Double, Double> {
get { AnimatablePair(startAngle.radians, endAngle.radians) }
set {
startAngle = .radians(newValue.first)
endAngle = .radians(newValue.second)
}
}
这是实现动画支持的核心代码。
animatableData:
Shape 协议要求实现的属性。
它定义了形状支持动画的属性(例如,弧形的角度变化)。
类型是 AnimatablePair<Double, Double>,表示一个由两个动画值组成的组合。这里将起始角度和结束角度的弧度值组合起来。
如何工作:
1)get:将 startAngle 和 endAngle 转换为弧度值,并包装成一个 AnimatablePair。
2)set:在动画过程中,SwiftUI 会逐步更新 newValue,将它解包回 startAngle 和 endAngle。
通过这种方式,当弧形的角度发生变化时,AnimatableArc 会通知 SwiftUI 使用平滑动画来绘制新的弧形。
3、路径绘制
func path(in rect: CGRect) -> Path {
var path = Path()
let center = CGPoint(x: rect.midX, y: rect.midY)
let radius = min(rect.width, rect.height) / 2
path.addArc(center: center,
radius: radius,
startAngle: startAngle,
endAngle: endAngle,
clockwise: false)
return path
}
这是 Shape 协议中绘制形状的核心方法。
path(in rect:):
接收一个矩形区域(CGRect),在这个区域内绘制形状。
返回一个 Path,定义了弧形的绘制逻辑。
代码解析:
1、center 和 radius:定义弧形的圆心和半径,圆心位于矩形的中心,半径是矩形宽高较小值的一半。
2、path.addArc:使用 addArc 方法在路径中添加一个弧形。
startAngle:弧形的起始角度。
endAngle:弧形的结束角度。
clockwise:是否顺时针绘制,这里设置为 false,表示逆时针绘制。
使用 AnimatableArc 的动画示例
struct ContentView: View {
@State private var progress: Double = 0.0
var body: some View {
VStack {
AnimatableArc(startAngle: .degrees(0), endAngle: .degrees(360 * progress))
.stroke(Color.blue, lineWidth: 5)
.frame(width: 200, height: 200)
.onTapGesture {
withAnimation(.linear(duration: 1)) {
progress = progress == 1.0 ? 0.0 : 1.0
}
}
Slider(value: $progress)
.padding()
}
}
}
视图代码解析
1、动画驱动
在 onTapGesture 中,通过 withAnimation 设置动画,改变 progress 值。
progress 变化会触发 endAngle 的更新,完成圆弧的动态绘制。
2、灵活性
可以轻松扩展为支持多参数的动画。例如,引入弧线颜色或宽度的动画时,可以嵌套更多 AnimatablePair。
AnimatableData 的作用
AnimatableData 的主要作用是将形状中需要动画的数据暴露给 SwiftUI 动画系统,确保数据在动画期间以平滑的过渡更新。
常见实现:
单值动画:CGFloat 或 Double
var animatableData: CGFloat
多值动画:AnimatablePair
var animatableData: AnimatablePair<Double, Double>
嵌套多值动画:
var animatableData: AnimatablePair<AnimatablePair<Double, Double>, Double>
总结
AnimatableData 是一个桥梁,用于 SwiftUI 的动画系统和形状内部的数据更新。
AnimatablePair 是一种数据类型,支持组合多个动画属性。
通过自定义 Shape 并实现 AnimatableData,可以轻松实现复杂的动画效果。
扩展知识
animatableData的实现
这段代码是实现 Shape 协议的核心部分,用于支持动画的动态数据。逐行分析其含义:
var animatableData: AnimatablePair<Double, Double> {
get { AnimatablePair(startAngle.radians, endAngle.radians) }
set {
startAngle = .radians(newValue.first)
endAngle = .radians(newValue.second)
}
}
animatableData 的含义
animatableData 是 Shape 协议中用于定义“动画数据”的属性。
SwiftUI 动画会自动监控 animatableData,并在动画期间逐步更新其值,从而实现动画效果。
AnimatablePair 和 Angle 的用法
1、AnimatablePair<Double, Double>:
AnimatablePair 是 SwiftUI 提供的一个类型,用于存储和管理两个动画值。
这里它存储两个 Double 类型的值:startAngle 和 endAngle 的弧度值。
动画时,SwiftUI 会逐步改变 AnimatablePair 中的 first 和 second 值。
2、get 部分:
通过 AnimatablePair 将 startAngle 和 endAngle 转换为弧度值并组合:
AnimatablePair(startAngle.radians, endAngle.radians)
startAngle.radians 和 endAngle.radians 是 Angle 的方法,将角度值转换为弧度表示的 Double 类型。
3、set 部分:
动画时,SwiftUI 会更新 newValue,它是一个包含动画目标值的 AnimatablePair。
newValue.first 和 newValue.second 分别表示起始角度和结束角度的目标弧度值。
这些值被重新解包并赋值给 startAngle 和 endAngle:
startAngle = .radians(newValue.first)
endAngle = .radians(newValue.second)
核心内容:AnimatablePair主要的作用是封装两个可以动画化的值,以便通过get 方法会返回一个 AnimatablePair<Double, Double>,表示起点和终点的弧度值。
一个变量用 AnimatableData,多个变量用 AnimatablePair 吗?
这是大致的规则,但具体实现会因需求而异:
单个动画变量
如果只有一个动画变量(如某个属性是单一值),可以直接使用 AnimatableData:
struct AnimatableCircle: Shape {
var radius: CGFloat
var animatableData: CGFloat { // 单一值,直接返回
get { radius }
set { radius = newValue }
}
func path(in rect: CGRect) -> Path {
Path { path in
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY),
radius: radius,
startAngle: .degrees(0),
endAngle: .degrees(360),
clockwise: false)
}
}
}
多个动画变量
如果需要同时动画化多个值(如两个或更多),可以用 AnimatablePair:
struct AnimatableArc: Shape {
var startAngle: Angle
var endAngle: Angle
var animatableData: AnimatablePair<Double, Double> { // 两个值使用 AnimatablePair
get { AnimatablePair(startAngle.radians, endAngle.radians) }
set {
startAngle = .radians(newValue.first)
endAngle = .radians(newValue.second)
}
}
}
更多动画变量
如果需要动画化三个或更多值,可以嵌套 AnimatablePair:
struct AnimatableShape: Shape {
var x: Double
var y: Double
var scale: Double
var animatableData: AnimatablePair<Double, AnimatablePair<Double, Double>> {
get { AnimatablePair(x, AnimatablePair(y, scale)) }
set {
x = newValue.first
y = newValue.second.first
scale = newValue.second.second
}
}
func path(in rect: CGRect) -> Path {
var path = Path()
let center = CGPoint(x: rect.midX + CGFloat(x), y: rect.midY + CGFloat(y))
path.addEllipse(in: CGRect(x: center.x, y: center.y, width: 100 * scale, height: 100 * scale))
return path
}
}
扩展总结
1、animatableData 的作用:
定义了哪些数据可以动画化。
SwiftUI 的动画系统会在动画期间逐步更新这些数据。
2、AnimatablePair 的使用规则:
单变量:直接使用单一类型(如 CGFloat、Double)的 animatableData。
两个变量:使用 AnimatablePair,例如 (startAngle, endAngle)。
多个变量:通过嵌套 AnimatablePair 表达任意数量的值。
3、为什么要这么设计?
SwiftUI 的动画系统需要明确的数据结构来计算和插值。
AnimatablePair 和 animatableData 为复杂的动画提供了灵活性,同时保持易用性和性能。
这让 SwiftUI 能够通过一套统一的动画机制支持从简单到复杂的动画场景。