SwiftUI VStack 动画看起来坏了 - 动画视图不会改变位置
SwiftUI VStack Animation looks broken - Animated View will not change position
让我们看一下下面的代码片段:
struct ContentView: View {
@State private var isViewHidden: Bool = false
let data = [1,2,3,4,5,6,7]
public var body: some View {
VStack {
Button("Hide", action: {
withAnimation {
isViewHidden.toggle()
}
})
ForEach(data, id: \.self) { _ in
VStack {
Text("Foo")
if isViewHidden {
Text("Bar").animation(nil)
}
}.padding().background(Color.green)
}
}
}
}
我希望 Text("Hide") 会为父 VStack 内的位置设置动画。
但它会坚持到它的最后一个位置并从那里淡出并动画回到那个位置。有没有可能给这个动画一个更自然的感觉,这样它就会在它的父级内部动画。
这是一个可视化问题的 gif:
问题是 Bar
是一个新的 View
,因此只是淡出或出现在它的最终目的地,所以它的位置没有动画,因为它之前不在屏幕上。
诀窍是让 Bar
一直显示在屏幕上,只调整它的 opacity
。 .offset
用于防止视图因单词 Bar
并不总是存在而增长。添加或删除空白文本视图 Text(" ")
以使视图像以前一样增长。
以下是提供更好看的动画的解决方法:
struct ContentView: View {
@State private var isViewHidden: Bool = false
let data = [1,2,3,4,5,6,7]
public var body: some View {
VStack {
Button("Hide", action: {
withAnimation {
isViewHidden.toggle()
}
})
ForEach(data, id: \.self) { _ in
VStack {
ZStack {
Text("Foo")
Text("Bar")
.offset(y: 28)
.opacity(isViewHidden ? 0 : 1)
}
if !isViewHidden {
Text(" ")
}
}.padding().background(Color.green)
}
}
}
}
这里是模拟器中的运行:
方案二:使用.matchedGeometryEffect
这是一个使用 .matchedGeometryEffect
在 Text("Bar").frame(height: 0)
和 Text("Bar")
之间设置动画的解决方案。
struct ContentView: View {
@Namespace var namespace
@State private var isViewHidden: Bool = false
let data = [1,2,3,4,5,6,7]
public var body: some View {
VStack {
Button("Hide") {
withAnimation {
isViewHidden.toggle()
}
}
ForEach(data, id: \.self) { id in
VStack {
Text("Foo")
if isViewHidden {
Text("Bar").frame(height: 0)
.matchedGeometryEffect(id: id, in: namespace)
} else {
Text("Bar")
.matchedGeometryEffect(id: id, in: namespace)
}
}
.padding().background(Color.green)
}
}
}
}
让我们看一下下面的代码片段:
struct ContentView: View {
@State private var isViewHidden: Bool = false
let data = [1,2,3,4,5,6,7]
public var body: some View {
VStack {
Button("Hide", action: {
withAnimation {
isViewHidden.toggle()
}
})
ForEach(data, id: \.self) { _ in
VStack {
Text("Foo")
if isViewHidden {
Text("Bar").animation(nil)
}
}.padding().background(Color.green)
}
}
}
}
我希望 Text("Hide") 会为父 VStack 内的位置设置动画。 但它会坚持到它的最后一个位置并从那里淡出并动画回到那个位置。有没有可能给这个动画一个更自然的感觉,这样它就会在它的父级内部动画。
这是一个可视化问题的 gif:
问题是 Bar
是一个新的 View
,因此只是淡出或出现在它的最终目的地,所以它的位置没有动画,因为它之前不在屏幕上。
诀窍是让 Bar
一直显示在屏幕上,只调整它的 opacity
。 .offset
用于防止视图因单词 Bar
并不总是存在而增长。添加或删除空白文本视图 Text(" ")
以使视图像以前一样增长。
以下是提供更好看的动画的解决方法:
struct ContentView: View {
@State private var isViewHidden: Bool = false
let data = [1,2,3,4,5,6,7]
public var body: some View {
VStack {
Button("Hide", action: {
withAnimation {
isViewHidden.toggle()
}
})
ForEach(data, id: \.self) { _ in
VStack {
ZStack {
Text("Foo")
Text("Bar")
.offset(y: 28)
.opacity(isViewHidden ? 0 : 1)
}
if !isViewHidden {
Text(" ")
}
}.padding().background(Color.green)
}
}
}
}
这里是模拟器中的运行:
方案二:使用.matchedGeometryEffect
这是一个使用 .matchedGeometryEffect
在 Text("Bar").frame(height: 0)
和 Text("Bar")
之间设置动画的解决方案。
struct ContentView: View {
@Namespace var namespace
@State private var isViewHidden: Bool = false
let data = [1,2,3,4,5,6,7]
public var body: some View {
VStack {
Button("Hide") {
withAnimation {
isViewHidden.toggle()
}
}
ForEach(data, id: \.self) { id in
VStack {
Text("Foo")
if isViewHidden {
Text("Bar").frame(height: 0)
.matchedGeometryEffect(id: id, in: namespace)
} else {
Text("Bar")
.matchedGeometryEffect(id: id, in: namespace)
}
}
.padding().background(Color.green)
}
}
}
}