下面是我的代码,我试图解决的问题是圆形形状中的图像看起来像平铺背景,当形状围绕其移动时固定在位置上,而我想要的结果是形状本身实际上是个人资料图片并且图像随着形状的移动而移动,与固定位置的背景不同。
如果我尝试使用clipShape(CustomCircleShape...
,也会发生同样的问题我很困惑如何将圆圈及其运动转化为图像。
struct WaveAnimationView: View {
@State private var screenCoveragePercent = 45.0
@State private var waveOffset = Angle(degrees: 0)
var body: some View {
ZStack {
let shape = CustomCircleShape(offSet: Angle(degrees: waveOffset.degrees), percent: screenCoveragePercent)
shape.fill(ImagePaint(image: Image("ryan"), sourceRect: CGRect(x: 0, y: 0, width: 1, height: 1), scale: 0.1)) .ignoresSafeArea(.all)
}
.onAppear {
withAnimation(.linear(duration: 1).repeatForever(autoreverses: false)) {
self.waveOffset = Angle(degrees: 360)
}
}
}
}
struct CustomCircleShape: Shape {
var offSet: Angle
var percent: Double
var animatableData: Double {
get { offSet.degrees }
set { offSet = Angle(degrees: newValue) }
}
func path(in rect: CGRect) -> Path {
var p = Path()
let lowestWave = 0.02
let highestWave = 1.00
let newPercent = lowestWave + (highestWave - lowestWave) * (percent / 100)
let waveHeight = 0.0020 * rect.height
let yOffSet = CGFloat(1 - newPercent) * (rect.height - 4 * waveHeight) + 2 * waveHeight
let startAngle = offSet
let endAngle = offSet + Angle(degrees: 360 + 10)
var circleCenter = CGPoint.zero
for angle in stride(from: startAngle.degrees, through: endAngle.degrees, by: 5) {
let x = CGFloat((angle - startAngle.degrees) / 360) * rect.width
let y = yOffSet + waveHeight * CGFloat(sin(Angle(degrees: angle).radians))
if x == rect.midX {
circleCenter = CGPoint(x: x, y: y)
}
}
if circleCenter == .zero {
circleCenter = CGPoint(x: rect.midX, y: yOffSet + waveHeight * CGFloat(sin(Angle(degrees: offSet.degrees + 180).radians)))
}
let circleRadius: CGFloat = 50
p.addEllipse(in: CGRect(x: circleCenter.x - circleRadius, y: circleCenter.y - circleRadius, width: 2 * circleRadius, height: 2 * circleRadius))
return p
}
}
编辑:下面是显示波浪动画的代码,其中圆圈(我希望是一个图像)正在模仿波浪的运动。
struct WaveAnimation: View {
@State private var screenCoveragePercent = 25.0
@State private var waveOffset = Angle(degrees: 0)
var body: some View {
ZStack {
let opacityOfWater = 0.25
Wave(offSet: Angle(degrees: waveOffset.degrees), percent: screenCoveragePercent)
.fill(Color.blue).opacity(opacityOfWater)
.ignoresSafeArea(.all)
CustomCircle(offSet: Angle(degrees: waveOffset.degrees), percent: screenCoveragePercent)
.fill(Color.blue).opacity(1.0)
.ignoresSafeArea(.all)
InvisibleSlider(percent: $screenCoveragePercent)
}
.onAppear {
withAnimation(.linear(duration: 1).repeatForever(autoreverses: false)) {
self.waveOffset = Angle(degrees: 360)
}
}
}
}
struct CustomCircle: Shape {
var offSet: Angle
var percent: Double
var animatableData: Double {
get { offSet.degrees }
set { offSet = Angle(degrees: newValue) }
}
func path(in rect: CGRect) -> Path {
var p = Path()
let lowestWave = 0.02
let highestWave = 1.00
let newPercent = lowestWave + (highestWave - lowestWave) * (percent / 100)
let waveHeight = 0.0020 * rect.height
let yOffSet = CGFloat(1 - newPercent) * (rect.height - 4 * waveHeight) + 2 * waveHeight
let startAngle = offSet
let endAngle = offSet + Angle(degrees: 360 + 10)
var circleCenter = CGPoint.zero
for angle in stride(from: startAngle.degrees, through: endAngle.degrees, by: 5) {
let x = CGFloat((angle - startAngle.degrees) / 360) * rect.width
let y = yOffSet + waveHeight * CGFloat(sin(Angle(degrees: angle).radians))
if x == rect.midX {
circleCenter = CGPoint(x: x, y: y)
}
}
if circleCenter == .zero {
circleCenter = CGPoint(x: rect.midX, y: yOffSet + waveHeight * CGFloat(sin(Angle(degrees: offSet.degrees + 180).radians)))
}
let circleRadius: CGFloat = 50
p.addEllipse(in: CGRect(x: circleCenter.x - circleRadius, y: circleCenter.y - circleRadius, width: 2 * circleRadius, height: 2 * circleRadius))
return p
}
}
struct Wave: Shape {
var offSet: Angle
var percent: Double
var animatableData: Double {
get { offSet.degrees }
set { offSet = Angle(degrees: newValue) }
}
func path(in rect: CGRect) -> Path {
var p = Path()
let lowestWave = 0.02
let highestWave = 1.00
let newPercent = lowestWave + (highestWave - lowestWave) * (percent / 100)
let waveHeight = 0.0020 * rect.height
let yOffSet = CGFloat(1 - newPercent) * (rect.height - 4 * waveHeight) + 2 * waveHeight
let startAngle = offSet
let endAngle = offSet + Angle(degrees: 360 + 10)
// Draw wave
p.move(to: CGPoint(x: 0, y: yOffSet + waveHeight * CGFloat(sin(offSet.radians))))
for angle in stride(from: startAngle.degrees, through: endAngle.degrees, by: 5) {
let x = CGFloat((angle - startAngle.degrees) / 360) * rect.width
let y = yOffSet + waveHeight * CGFloat(sin(Angle(degrees: angle).radians))
p.addLine(to: CGPoint(x: x, y: y))
}
p.addLine(to: CGPoint(x: rect.width, y: rect.height))
p.addLine(to: CGPoint(x: 0, y: rect.height))
p.closeSubpath()
return p
}
}
您的代码正在对形状的 path 进行动画处理,相对于其边界(
rect
参数)。
您应该创建一个
Image(...).clipShape(Circle())
,并向其添加 .offset(y: ...)
。然后您可以为整个图像的 y 偏移设置动画。
我会设计
Wave
,使其填充整个给定的rect
。即删除percent
。可以使用 Spacer
中的 VStack
调整波浪覆盖的空间大小。
struct Wave: Shape {
var offset: Angle
let maxHeight: CGFloat
var animatableData: Double {
get { offset.degrees }
set { offset = Angle(degrees: newValue) }
}
func path(in rect: CGRect) -> Path {
Path { p in
p.move(to: rect.origin)
for deg in stride(from: 0, through: 360, by: 5) {
let angle = Angle.degrees(Double(deg)) + offset
let height = sin(angle.radians) * maxHeight
p.addLine(to: .init(x: Double(deg) / 360 * rect.size.width + rect.minX, y: height + rect.minY))
}
p.addLine(to: .init(x: rect.maxX, y: rect.maxY))
p.addLine(to: .init(x: rect.minX, y: rect.maxY))
p.closeSubpath()
}
}
}
现在我们将图像放在它上面。图像的 y 偏移量是通过数学计算得出的。查看
yOffset
的实现如何对应于 Wave.path(in:)
的实现。
struct WaveAndCircle: View, Animatable {
var waveOffset: Angle
let maxWaveHeight: CGFloat = 20
var animatableData: Double {
get { waveOffset.degrees }
set { waveOffset = Angle(degrees: newValue) }
}
var body: some View {
ZStack(alignment: .top) {
Wave(offset: waveOffset, maxHeight: maxWaveHeight)
.fill(.blue.opacity(0.25))
Image("my_image")
.resizable()
.frame(width: 50, height: 50)
.clipShape(Circle())
// change the alignment guide of the image, so that its center
// is aligned to the top of the wave
.alignmentGuide(.top, computeValue: { dimension in
dimension[VerticalAlignment.center]
})
.offset(y: yOffset)
}
}
var yOffset: CGFloat {
let offsetAngle = waveOffset + .degrees(180)
let sinResult = sin(offsetAngle.radians)
return sinResult * maxWaveHeight
}
}
最后,将所有内容放在一起。
struct WaveAnimation: View {
@State private var screenCoveragePercent = 25.0
@State private var waveOffset = Angle(degrees: 0)
var body: some View {
GeometryReader { geo in
VStack {
Spacer().frame(height: geo.size.height * (1 - screenCoveragePercent))
WaveAndCircle(waveOffset: waveOffset)
}
}
.onAppear {
withAnimation(.linear(duration: 1).repeatForever(autoreverses: false)) {
self.waveOffset = Angle(degrees: 360)
}
}
}
}