SwiftUI学习(6)-SwiftUI动画入门Part3
2021, Dec 04
SwiftUI动画入门Part 3-给自定义Shape添加动画

本文将介绍如何绘制上图中的正弦波动画。
关于自定义Shape,可以参考之前文章:
自定义正弦波Shape
正弦图形函数:y = sin(x * 波长)* 振幅, 定义波长等于 1/4的宽度, 振幅为1/3高度,原点(x: -波长,y: 1/2 高度),WaveShape代码如下:
struct WaveShape: Shape{
func path(in rect: CGRect) -> Path {
var points = [CGPoint]()
let h = rect.height / 2
// 振幅
let amplitude = rect.height / 3
// 波长
let wavelength = rect.width / 4
for i in 0 ..< Int(rect.width) {
let x = CGFloat(i) - wavelength
let y = sin(2 * CGFloat.pi / wavelength * x) * amplitude + h
points.append(CGPoint(x: CGFloat(x), y: y))
}
return Path{ path in
path.move(to: points.first!)
points.forEach { p in
path.addLine(to: p)
}
path.addEllipse(in: CGRect(origin: points.last!.applying(CGAffineTransform(translationX: -5, y: -5)), size: CGSize(width: 10, height: 10)))
}
}
}
在view 中显示WaveShape并上颜色:
var body: some View {
WaveShape()
.stroke(LinearGradient(colors: [Color(hue: 13 / 360, saturation: 0.79, brightness: 0.97), Color(hue: 40 / 360, saturation: 0.66, brightness: 0.93)], startPoint: UnitPoint(x: 0, y: 0.5), endPoint: UnitPoint(x: 1, y: 0.5)), lineWidth: 7)
}
得到一个静态正弦波页面:

给正弦波添加动画
SwiftUI动画的背后是一个Animatable的协议, Animatable大概张这样:
protocol Animatable{
associatedtype AnimatableData : VectorArithmetic
var animatableData: AnimatableData
}
当使用View进行动画的时候, SwiftUI会多出生成改View,并给animatableData不同值。
Shape协议继承了Animatable, 通过实现animatableData就可以实现动画了。
在WaveShape添加一个time属性,并实现Animatable协议,将time属性作为动画参数:
...
var time: CGFloat
var animatableData: CGFloat{
get{
return time
}
set{
time = newValue
}
}
...
修改path函数,将绘制和time关联起来:
...
// 每个点y根据时间偏移
let y = sin(2 * CGFloat.pi / wavelength * x + wavelength * time) * a + h
...
在显示正弦波的View修改如下:
@State var animation: Bool = false
var body: some View {
WaveShape(time: self.animation ? 1: 0)
.stroke(LinearGradient(colors: [Color(hue: 13 / 360, saturation: 0.79, brightness: 0.97), Color(hue: 40 / 360, saturation: 0.66, brightness: 0.93)], startPoint: UnitPoint(x: 0, y: 0.5), endPoint: UnitPoint(x: 1, y: 0.5)), lineWidth: 7)
.onAppear {
withAnimation(.linear(duration: 60).repeatForever(autoreverses: false)) {
self.animation.toggle()
}
}
}
运行效果就如一开始那样:
