如何从 SwiftUI 制作路径动画?

问题描述 投票:0回答:1

我知道如何为 StrokeStart/StrokeEnd 等单个属性或属性对设置动画。我正在谈论您可以通过 CoreAnimation 执行哪些操作:创建一个动画,以动画方式将一条路径替换为具有相同数量控制点的不同路径。像这样的东西:

enter image description here

我用这个项目在 Swift/UIKit 中创建了它:

https://github.com/DuncanMC/RandomBlobs

该项目使用 CoreAnimation 并创建一个 CABasicAnimation,该 CABasicAnimation 构建 CAShapeLayer 并用具有相同控制点数量的新路径替换图层中的路径。

我希望 SwiftUI 使用其 Shape 或 Path 对象支持相同的功能,但尚未弄清楚如何做到这一点。 (是的,我知道我可以包装 UIKit UIView 以在 SwiftUI 中使用。我还没有这样做,但我知道这是可能的。我正在寻找原生 SwiftUI 解决方案。)

swift animation core-animation cgpath
1个回答
0
投票

在 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()
                }
            }
        }
    }
}

同样,我在这里保持简单,只是随机选择一组要设置动画的点,因此大多数时候路径会与自身相交。在任何情况下,您都可以看到每个点都根据需要进行动画处理。

© www.soinside.com 2019 - 2024. All rights reserved.