我想要在选项卡之间切换动画,但动画非常困难。我尝试使用匹配的几何体,但动画很古怪。可能缺乏对匹配几何形状的理解。
期望:
点击选项卡将显示图像图标和胶囊后面的选项卡标题。 未点击的选项卡不会显示标题,也不会在其后面显示胶囊。一旦选择了新选项卡,胶囊将导航到所选选项卡。并且先前选择的选项卡的标题将隐藏。
import SwiftUI
struct Tabbar: View {
@Namespace private var animation
@State var selectedTab: RootTab = .home
var body: some View {
ZStack {
HStack {
ForEach(RootTab.allCases, id: \.self) { tab in
TabButton(tab: tab)
}
}
.padding(.horizontal, 30)
.padding(.vertical, 10)
.background(Color.blue.opacity(0.4))
.cornerRadius(30, corners: .allCorners)
}
.frame(maxWidth: .infinity)
.background(Color(uiColor: .systemBackground))
}
private func TabButton(tab: RootTab) -> some View {
HStack {
Image(systemName: tab.systemName)
.font(.title2)
.foregroundStyle(.white)
.bold()
if selectedTab == tab {
Text(tab.title)
.font(.callout)
.bold()
.foregroundColor(.white)
.animation(.default, value: selectedTab)
}
}
.onTapGesture {
withAnimation {
selectedTab = tab
}
}
.if(selectedTab == tab) { view in
view
.padding(.vertical, 8)
.padding(.horizontal, 8)
.background { Color.blue }
.cornerRadius(15, corners: .allCorners)
.animation(.default, value: selectedTab)
}
}
}
enum RootTab: String, CaseIterable {
case home
case profile
var systemName: String {
switch self {
case .home: return "house"
case .profile: return "person.crop.circle"
}
}
var title: String {
switch self {
case .home: return "Home"
case .profile: return "Profile"
}
}
}
extension View {
@ViewBuilder
func `if`(_ condition: @autoclosure () -> Bool, transform: (Self) -> some View) -> some View {
if condition() {
transform(self)
} else {
self
}
}
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
clipShape(RoundedCorner(radius: radius, corners: corners))
}
}
正如您所建议的,
.matchedGeometryEffect
可用于实现此动画:
移动背景应显示为
ZStack
的第一层(换句话说,在按钮后面)。
使用
.matchedGeometryEffect
和 isSource: false
将移动背景的位置和大小与所选选项卡按钮相匹配。标签按钮的id用作效果的id。
由于背景标记的大小由所选按钮决定,因此应将填充应用于按钮本身。
选项卡按钮使用
withAnimation
执行更改,因此不需要任何 .animation
修饰符。
不需要视图扩展
.if
。
struct Tabbar: View {
@Namespace private var animation
@State var selectedTab: RootTab = .home
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 15)
.fill(.blue)
.matchedGeometryEffect(id: selectedTab, in: animation, isSource: false)
HStack {
ForEach(RootTab.allCases, id: \.self) { tab in
TabButton(tab: tab)
.padding(.vertical, 8)
.padding(.horizontal, 8)
.matchedGeometryEffect(id: tab, in: animation)
}
}
.padding(.horizontal, 30)
.padding(.vertical, 10)
.background(Color.blue.opacity(0.4))
.cornerRadius(30, corners: .allCorners)
}
.frame(maxWidth: .infinity)
.background(Color(uiColor: .systemBackground))
}
private func TabButton(tab: RootTab) -> some View {
HStack {
Image(systemName: tab.systemName)
.font(.title2)
.foregroundStyle(.white)
.bold()
if selectedTab == tab {
Text(tab.title)
.font(.callout)
.bold()
.foregroundColor(.white)
}
}
.onTapGesture {
withAnimation {
selectedTab = tab
}
}
}
}