SwiftUI&ArkUI-曲线动画Path和路径动画motionPath

OpenHarmony Path ArkUI 高性能 motionPath 动效 三次贝塞尔曲线 曲线动画 SwiftUI

SwiftUI通过Path可以绘制路径动画,通过addCurve可用绘制三次贝塞尔曲线。

ArkUI是鸿蒙的核心UI布局框架,使用motionPath绘制路径动画,通过绘制路径可以自定义三次贝塞尔曲线。

对比分析

SwiftUI ArkUI
Path motionPath
The outline of a 2D shape. 设置组件进行位移动画时的运动路径。
struct Path .motionPath({})
addCurve 绘制路径
addCurve(to:control1:control2:) Mstart.x start.y C -200 50, -150 200, end.x end.y

在SwiftUI中,可用通过设置三个关键点(结束点、控制1、控制2)调整贝塞尔曲线。

mutating func addCurve(
    to end: CGPoint,
    control1: CGPoint,
    control2: CGPoint
)

在ArkUI中,贝塞尔曲线的参数是硬编码,字符串各个参数中间用空格隔开。

Button('click me').margin(50)
        // 执行动画:从起点移动到(300,200),再到(300,500),再到终点
        .motionPath({ path: 'Mstart.x start.y L300 200 L300 500 Lend.x end.y', from: 0.0, to: 1.0, rotatable: true })

硬编码容易出现拼写错误,参数不易理解问题。

SwiftUI通过Path应用实践

通过以下三个结构体可以完成一个简单的路径动画:

  1. 贝塞尔曲线动画
  2. 路径动画
  3. 构建视图
SwiftUI&ArkUI-曲线动画Path和路径动画motionPath_第1张图片

实践代码

贝塞尔曲线动画

struct InfinityShape2: Shape {
    func path(in rect: CGRect) -> Path {
        return InfinityShape2.createInfinityPath(in: rect)
    }

    static func createInfinityPath(in rect: CGRect) -> Path {
        let height = rect.size.height
        let width = rect.size.width
        let heightFactor = height/4
        let widthFactor = width/4

        var path = Path()
        
        path.move(to: CGPoint(x:widthFactor, y: heightFactor * 6))
        path.addCurve(to: CGPoint(x:widthFactor, y: heightFactor), control1: CGPoint(x:0, y: heightFactor * 6), control2: CGPoint(x:0, y: heightFactor))

        path.move(to: CGPoint(x:widthFactor, y: heightFactor))
        path.addCurve(to: CGPoint(x:widthFactor * 3, y: heightFactor * 3), control1: CGPoint(x:widthFactor * 2, y: heightFactor), control2: CGPoint(x:widthFactor * 2, y: heightFactor * 3))

        path.move(to: CGPoint(x:widthFactor * 3, y: heightFactor * 3))
        path.addCurve(to: CGPoint(x:widthFactor * 3, y: heightFactor), control1: CGPoint(x:widthFactor * 4 + 5, y: heightFactor * 3), control2: CGPoint(x:widthFactor * 4 + 5, y: heightFactor))

        path.move(to: CGPoint(x:widthFactor * 3, y: heightFactor))
        path.addCurve(to: CGPoint(x:widthFactor, y: heightFactor * 6), control1: CGPoint(x:widthFactor * 1, y: heightFactor), control2: CGPoint(x:widthFactor * 2, y: heightFactor * 6))
        
        return path
    }
}

路径动画

struct FollowEffect2: GeometryEffect {
    var pct: CGFloat = 0
    let path: Path
    var rotate = true

    var animatableData: CGFloat {
        get { return pct }
        set { pct = newValue }
    }

    func effectValue(size: CGSize) -> ProjectionTransform {

        if !rotate {
            let pt = percentPoint(pct)

            return ProjectionTransform(CGAffineTransform(translationX: pt.x, y: pt.y))
        } else {
            // Calculate rotation angle, by calculating an imaginary line between two points
            // in the path: the current position (1) and a point very close behind in the path (2).
            let pt1 = percentPoint(pct)
            let pt2 = percentPoint(pct - 0.01)

            let a = pt2.x - pt1.x
            let b = pt2.y - pt1.y

            let angle = a < 0 ? atan(Double(b / a)) : atan(Double(b / a)) - Double.pi

            let transform = CGAffineTransform(translationX: pt1.x, y: pt1.y).rotated(by: CGFloat(angle))

            return ProjectionTransform(transform)
        }
    }

    func percentPoint(_ percent: CGFloat) -> CGPoint {

        let pct = percent > 1 ? 0 : (percent < 0 ? 1 : percent)

        let f = pct > 0.999 ? CGFloat(1-0.001) : pct
        let t = pct > 0.999 ? CGFloat(1) : pct + 0.001
        let tp = path.trimmedPath(from: f, to: t)

        return CGPoint(x: tp.boundingRect.midX, y: tp.boundingRect.midY)
    }
}

最后,构建view

struct ExamplePlane: View {
    @State private var flag = false

    var body: some View {
        GeometryReader { proxy in
            ZStack(alignment: .topLeading) {
                
                // Draw the Infinity Shape
                InfinityShape2().stroke(Color.red, style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .miter, miterLimit: 0, dash: [7, 7], dashPhase: 0))
                    .foregroundColor(.blue)
                    .frame(width: proxy.size.width, height: 300)

                // Animate movement of Image
                // Image(systemName: "airplane")
                Image(systemName: "airplane").resizable().foregroundColor(Color.red)
                    .frame(width: 80, height: 80).offset(x: -40, y: -40)
                    .modifier(FollowEffect2(pct: self.flag ? 1 : 0, path: InfinityShape2.createInfinityPath(in: CGRect(x: 0, y: 0, width: proxy.size.width, height: 300)), rotate: true))
                    .onAppear {
                        withAnimation(Animation.linear(duration: 4.0).repeatForever(autoreverses: false)) {
                            self.flag.toggle()
                        }
                    }

                }.frame(alignment: .topLeading)
        }
        .padding(20)
    }
}

你可能感兴趣的:(鸿蒙应用,arkui开发,open,harmony开发,swiftui,ios,swift,openharmony,arkui)