我拥有三处房产。两种在变化时具有不同的动画,一种没有动画。
struct Stick: View {
var color: Color
var body: some View {
GeometryReader { global in
Path { path in
path.move(to: CGPoint(x: global.size.width/2, y: 0))
path.addLine(to: CGPoint(x:global.size.width/2,y: global.size.height))
}
.stroke(color,lineWidth: 200)
}
}
}
struct SuperStick: View {
@Binding var progress: Float
@State var offset: CGFloat = 0
@State var color: Color = .blue.opacity(0.5)
var body: some View {
GeometryReader { global in
ZStack(alignment: .bottom) {
Stick(color: color)
.onAppear {
if offset >= 1 {
offset = 0
}
offset += 1
color = .green.opacity(0.5)
}
}
.frame(width: global.size.width, height: global.size.height)
// Animation 1
// .animation(.linear(duration: 0.1).repeatForever(autoreverses: true), value: color)
// Animation 2
.position(x: global.size.width * (0.3+0.4*offset),y: global.size.height/2)
.animation(.linear(duration: 1).repeatForever(autoreverses: false), value: offset)
// Animation 3
.offset(y:CGFloat(1-progress)*global.size.height)
.animation(.easeInOut(duration: 0.4), value: progress)
}
}
}
struct TestView: View {
@State var progress: Float = 0.6
var body: some View {
ZStack{
SuperStick(progress: $progress)
.edgesIgnoringSafeArea(.all)
VStack {
Stepper("progress: \(String(format: "%.1f", progress))",value: $progress, in: 0.0...1.0, step: 0.2)
.padding(.horizontal,40)
}
}
}
}
#Preview {
TestView()
}
我们可以看到Animation1被注释了,所以应该只有
offset
和progress
的动画。但是,我们可以看到 color
也获得了动画效果,其速度与 offset
相同。
然后我尝试为
color
添加动画,希望color
有自己的动画(只需取消注释Animation1
下面的行),然而,这一次,offset
具有与color
相同的动画它自己的Animation2
。
然后最奇怪的事情发生了。
如果你评论
Animation1
但保留Animation2
,然后尝试更改progress
,你会发现虽然我们只有Animation2
和Animation3
,而color
只是受到offset
的某种影响
拥有 Animation2
,
当您更改
progress
时,Animation2
上的 offset
会停止(被 Animation3
打断),但 color
会不断变化...
为什么这一切会发生?
您遇到这些问题是因为,例如,当您使用步进器更改进度时,视图会重新渲染,但由于视图已经显示并且动画已在 onAppear 中初始化,因此它们没有得到正确的重新初始化。我正在专门谈论偏移动画。另外,动画修改器文档是这样说的:
当指定值时,将给定动画应用于此视图 变化。
所以,当你使用这样的东西时,会发生什么:
.animation(.linear(duration: 1).delay(0.1).repeatForever(autoreverses: true), value: color)
是当颜色改变时,它会动画化整个视图,而不仅仅是颜色值。要仅对颜色值进行动画处理,您应该使用 withAnimation 函数,该函数可以对使用 color 属性的代码的任何部分进行动画处理。
我已经修改了你的代码,如下所示:
struct SuperStick: View {
@Binding var progress: Float
@State var offset: CGFloat = 0
@State var color: Color = .blue.opacity(0.5)
var body: some View {
let _ = print("Rendered")
GeometryReader { global in
ZStack(alignment: .bottom) {
Stick(color: color)
.onAppear {
/// Called for the first time. The animation now animates from 0 to 1.
offset = 1
withAnimation(.linear(duration: 1).delay(0.1).repeatForever(autoreverses: true)) {
color = .green.opacity(0.5)
}
}
}
.frame(width: global.size.width, height: global.size.height)
// Animation 1
//.animation(.linear(duration: 1).delay(0.1).repeatForever(autoreverses: true), value: color)
// Animation 2
.position(x: global.size.width * (0.3+0.4*offset),y: global.size.height/2)
.animation(.linear(duration: 5).repeatForever(autoreverses: false), value: offset)
// Animation 3
.offset(y:CGFloat(1-progress)*global.size.height)
.animation(.easeInOut(duration: 0.4), value: progress)
.onChange(of: progress) { oldValue, newValue in
withAnimation(.linear(duration: 1).repeatForever(autoreverses: false)) {
/// Called when progress changes. This resets the animation and make it start again.
if offset == 1 {
offset = 0
} else {
offset = 1
}
}
}
}
}
}
我使用了 iOS 17 的 onChange 修饰符,但你也可以使用旧的,你只需要在闭包中传递一个参数而不是两个。
在 onChange 中,我重新初始化偏移动画,以便当基于进度值更改发生重新渲染时,动画也会刷新。
我希望这个解释是清楚的,不要犯错误。
让我知道您的想法以及此代码是否适合您!