我知道如何为 StrokeStart/StrokeEnd 等单个属性或属性对设置动画。我正在谈论您可以通过 CoreAnimation 执行哪些操作:创建一个动画,以动画方式将一条路径替换为具有相同数量控制点的不同路径。像这样的东西:
我用这个项目在 Swift/UIKit 中创建了它:
https://github.com/DuncanMC/RandomBlobs
该项目使用 CoreAnimation 并创建一个 CABasicAnimation,该 CABasicAnimation 构建 CAShapeLayer 并用具有相同控制点数量的新路径替换图层中的路径。
我希望 SwiftUI 使用其 Shape 或 Path 对象支持相同的功能,但尚未弄清楚如何做到这一点。 (是的,我知道我可以包装 UIKit UIView 以在 SwiftUI 中使用。我还没有这样做,但我知道这是可能的。我正在寻找原生 SwiftUI 解决方案。)
Animatable
。这个想法是你声明一些 animatableData
,SwiftUI 将在动画的每一帧期间设置它们,在开始值和结束值之间进行插值。
animatableData
需要是 VectorArithmetic
类型。对于此示例,假设形状有 5 个点,那么 animatableData
将表示一个包含 10 个值的向量。有一个内置的 AnimatablePair
允许您将两个 VectorArithmetic
组合在一起,但在这个例子中我将手写这样的类型:
struct FivePoints: VectorArithmetic, AdditiveArithmetic {
// this random function is for later...
static func random() -> FivePoints {
.init(p1: .random(), p2: .random(), p3: .random(), p4: .random(), p5: .random())
}
static func -(lhs: FivePoints, rhs: FivePoints) -> FivePoints {
.init(
p1: lhs.p1 - rhs.p1,
p2: lhs.p2 - rhs.p2,
p3: lhs.p3 - rhs.p3,
p4: lhs.p4 - rhs.p4,
p5: lhs.p5 - rhs.p5
)
}
static func +(lhs: FivePoints, rhs: FivePoints) -> FivePoints {
.init(
p1: lhs.p1 + rhs.p1,
p2: lhs.p2 + rhs.p2,
p3: lhs.p3 + rhs.p3,
p4: lhs.p4 + rhs.p4,
p5: lhs.p5 + rhs.p5
)
}
var p1: CGPoint
var p2: CGPoint
var p3: CGPoint
var p4: CGPoint
var p5: CGPoint
public mutating func scale(by rhs: Double) {
p1.x *= rhs
p1.y *= rhs
p2.x *= rhs
p2.y *= rhs
p3.x *= rhs
p3.y *= rhs
p4.x *= rhs
p4.y *= rhs
p5.x *= rhs
p5.y *= rhs
}
public var magnitudeSquared: Double {
pow(p1.x, 2) + pow(p2.x, 2) + pow(p3.x, 2) + pow(p4.x, 2) + pow(p5.x, 2) +
pow(p1.y, 2) + pow(p2.y, 2) + pow(p3.y, 2) + pow(p4.y, 2) + pow(p5.y, 2)
}
public static var zero: FivePoints {
.init(p1: .zero, p2: .zero, p3: .zero, p4: .zero, p5: .zero)
}
}
func +(lhs: CGPoint, rhs: CGPoint) -> CGPoint {
CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
func -(lhs: CGPoint, rhs: CGPoint) -> CGPoint {
CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
}
extension CGPoint {
static func random() -> CGPoint {
.init(x: Double.random(in: 0..<300), y: Double.random(in: 0..<300))
}
}
那么就可以写这样的
View
:
struct FivePointShape: View, Animatable {
var points: FivePoints
var animatableData: FivePoints {
get { points }
set { points = newValue }
}
var body: some View {
Path { p in
p.addLines([points.p1, points.p2, points.p3, points.p4, points.p5])
p.closeSubpath()
}
.fill(.yellow)
}
}
如果这些点与其他一些
Shape
相关,这也可能是 CGRect
(回想一下 CGRect
的 Shape.path(in:)
参数)。在这个例子中,我将保持简单并说 CGPoint
是绝对的。
用途:
struct ContentView: View {
@State var points = FivePoints.random()
var body: some View {
ZStack {
FivePointShape(points: points)
Button("Animate!") {
withAnimation {
points = .random()
}
}
}
}
}
同样,我在这里保持简单,只是随机选择一组要设置动画的点,因此大多数时候路径会与自身相交。在任何情况下,您都可以看到每个点都根据需要进行动画处理。