You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SwiftUI中实现同控制点数量的路径替换动画

原生SwiftUI实现同控制点数量路径的平滑过渡动画

我已经能实现strokeStart/strokeEnd这类属性的动画,现在想在SwiftUI里复刻CoreAnimation的路径替换效果——让两个控制点数量完全一致的路径平滑过渡,效果如下:

路径平滑过渡动画

之前在UIKit中通过CABasicAnimation给CAShapeLayer替换同控制点数量的新路径实现了该效果,但现在想找纯SwiftUI的解决方案,不想用UIView封装的方式。


实现方案:自定义可动画Shape

SwiftUI的Shape遵循Animatable协议,我们可以通过自定义Shape,将路径的控制点作为可动画属性,实现路径的平滑过渡。

步骤:

  1. 自定义Shape,存储路径的控制点数组(必须保证前后路径的点数、点类型一致)
  2. 实现animatableData,将控制点的x、y坐标拆分为可动画的数组
  3. 在path(in:)中根据当前控制点构建路径

示例代码:

struct AnimatedBezierShape: Shape {
    // 存储贝塞尔曲线的控制点(示例为三次贝塞尔,每组包含起点+2个控制点+终点)
    var controlPoints: [CGPoint]
    
    // 实现可动画数据,将x、y坐标分别转为数组
    var animatableData: AnimatablePair<[CGFloat], [CGFloat]> {
        get {
            let xs = controlPoints.map { $0.x }
            let ys = controlPoints.map { $0.y }
            return AnimatablePair(xs, ys)
        }
        set {
            let count = min(newValue.first.count, newValue.second.count)
            controlPoints = (0..<count).map {
                CGPoint(x: newValue.first[$0], y: newValue.second[$0])
            }
        }
    }
    
    func path(in rect: CGRect) -> Path {
        var path = Path()
        guard controlPoints.count >= 4 else { return path }
        
        // 按三次贝塞尔曲线构建路径(示例为单条曲线,可扩展为多条)
        path.move(to: controlPoints[0])
        path.addCurve(to: controlPoints[3],
                      control1: controlPoints[1],
                      control2: controlPoints[2])
        return path
    }
}

// 使用示例
struct PathTransitionView: View {
    @State private var showTargetPath = false
    
    // 原始路径控制点(三次贝塞尔,4个点)
    let originalPoints: [CGPoint] = [
        CGPoint(x: 50, y: 150),
        CGPoint(x: 100, y: 50),
        CGPoint(x: 200, y: 250),
        CGPoint(x: 250, y: 150)
    ]
    
    // 目标路径控制点(同样4个点,和原始路径类型一致)
    let targetPoints: [CGPoint] = [
        CGPoint(x: 50, y: 150),
        CGPoint(x: 100, y: 250),
        CGPoint(x: 200, y: 50),
        CGPoint(x: 250, y: 150)
    ]
    
    var body: some View {
        AnimatedBezierShape(controlPoints: showTargetPath ? targetPoints : originalPoints)
            .stroke(.orange, lineWidth: 4)
            .frame(width: 300, height: 300)
            .onTapGesture {
                withAnimation(.easeInOut(duration: 1.5)) {
                    showTargetPath.toggle()
                }
            }
    }
}

注意事项:

  • 必须保证前后路径的控制点数量、类型完全匹配:比如都是折线的点,或都是同数量的贝塞尔曲线控制点(三次贝塞尔每组4个点,两组路径的点数要一致),否则动画会出现跳变
  • 如果是复杂路径(多段曲线/折线组合),需要确保两组路径的段数、每段的控制点数量一一对应
  • animatableData通过拆分x、y坐标数组,让SwiftUI能逐点插值动画,实现平滑过渡

内容的提问来源于stack exchange,提问作者Duncan C

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 09:25:03