SwiftUI 使用 matchedGeometryEffect 时如何同步父视图与子视图动画
解决方案
你遇到的问题是子视图没有跟随父视图的matchedGeometry动画同步触发显示效果,只需要新增少量代码即可修复,修改步骤如下:
- 给
MenuView新增show参数,接收父视图的显隐状态 - 给
MenuView的内容容器添加透明度控制,和与父视图时长一致的动画绑定
完整修改后代码
struct ContentView: View { @State var show = false @Namespace var namespace var body: some View { VStack(spacing: 0) { HStack(spacing: 0) { Spacer() Button(action: { withAnimation(.linear(duration: 1.2)) { // 延长时长用于演示问题 show.toggle() } }) { Image(systemName: "ellipsis.circle") .font(.title3) .background( !show ? Color.clear .matchedGeometryEffect(id: "topMenuToggle", in: namespace, properties: .position) : nil ) } } .padding() Color.clear .overlay( show ? MenuView(namespace: namespace, show: show) : nil // 新增传参 , alignment: .topTrailing ) } .foregroundColor(.white) .background(Color.black.ignoresSafeArea()) } } struct MenuView: View { let namespace: Namespace.ID let show: Bool // 新增参数 var body: some View { VStack(spacing: 0) { Button(action: { }) { HStack(spacing: 0) { Text("Save video") .font(.body) Spacer(minLength: 0) Image(systemName: "arrow.down.circle") .resizable() .aspectRatio(contentMode: .fit) .frame(height: 20) } } .padding(.top, 18) .padding(.bottom, 18) .padding(.horizontal, 14) Rectangle() .fill(Color.white.opacity(0.1)) .frame(maxWidth: .infinity, maxHeight: 1) Button(action: { }) { HStack(spacing: 0) { Text("Report") .font(.body) Spacer(minLength: 0) Image(systemName: "exclamationmark.bubble") .resizable() .aspectRatio(contentMode: .fit) .frame(height: 20) } } .padding(.top, 16) .padding(.bottom, 18) .padding(.horizontal, 14) } .foregroundColor(.white) .frame(width: 250) .opacity(show ? 1 : 0) // 新增透明度控制 .animation(.linear(duration: 1.2), value: show) // 新增同步动画 .background( Color.white.opacity(0.2) .matchedGeometryEffect(id: "topMenuToggle", in: namespace, anchor: .bottomLeading) ) .cornerRadius(12) } }
原理说明
SwiftUI 中matchedGeometryEffect只会作用于添加了该修饰符的视图本身,不会自动将动画同步到其子视图。子视图默认会在父视图插入渲染树时直接显示,没有过渡效果。通过手动给子容器添加和父动画时长一致的透明度动画,就可以实现按钮内容和背景过渡完全同步的效果。
如果不需要半透明过渡,也可以将动画替换为裁剪过渡:把opacity相关代码换成.clipShape(RoundedRectangle(cornerRadius: 12)).animation(.linear(duration: 1.2), value: show),可以实现子视图跟随背景展开逐步显示的效果。
内容的提问来源于stack exchange,提问作者nurmat
相关产品推荐
相关产品推荐

