如何借助matchedGeometryEffect实现跨视图层级的3D旋转过渡动画?
用matchedGeometryEffect实现过渡+3D旋转的可行方案
你希望通过matchedGeometryEffect让地球图标在两个视图间过渡时同步完成3D旋转,目前的问题是条件渲染切换视图时,旋转动画会中断——因为被切换的视图会被销毁,绑定的旋转变量无法维持动画连贯性。
可行实现思路
核心是让地球图标始终保留在视图层级中,不随条件渲染被移除,用matchedGeometryEffect同步它的位置/大小到两个子视图的"锚点",同时独立控制旋转动画,既保留matchedGeometryEffect的优雅过渡,又保证旋转平滑完成。
修改后的代码
struct ContentView: View { @State var mode: Bool = true @Namespace private var compassAnimation @State var rotation: CGFloat = 0 var body: some View { ZStack { // 始终保留两个视图,用opacity控制显示/隐藏,避免视图销毁 View1(namespace: compassAnimation) .opacity(mode ? 0 : 1) .disabled(mode) View2(namespace: compassAnimation) .opacity(mode ? 1 : 0) .disabled(!mode) // 实际的地球图标,放在最上层,通过matchedGeometryEffect同步位置 Image(systemName: "globe") .resizable() .aspectRatio(contentMode: .fit) .matchedGeometryEffect(id: "globe", in: compassAnimation) .rotation3DEffect(.degrees(rotation), axis: (x: 0, y: 1, z: 0), perspective: 0) // 目标效果预览 HStack { VStack { Text("目标效果:") Text("(地球这样旋转)") } Image(systemName: "globe") .resizable() .aspectRatio(contentMode: .fit) .frame(width: 80) .rotation3DEffect(.degrees(rotation), axis: (x: 0, y: 1, z: 0), perspective: 0) } .padding() .background(Color.green) .clipShape(Capsule()) .padding(.bottom, 200) Button { withAnimation(.easeInOut(duration: 0.8)) { mode.toggle() // 累加旋转角度,实现连续平滑的旋转 rotation += 360 } } label: { Text("切换 \(mode)") } .buttonStyle(BorderedProminentButtonStyle()) } } } struct View1: View { let namespace: Namespace.ID var body: some View { ZStack(alignment: .bottomLeading) { Color.purple .frame(maxWidth: .infinity, maxHeight: .infinity) // 透明锚点,用于同步地球的位置和大小 Color.clear .frame(width: 100, height: 100) .padding() .matchedGeometryEffect(id: "globe", in: namespace) } } } struct View2: View { let namespace: Namespace.ID var body: some View { ZStack(alignment: .center) { Color.blue .ignoresSafeArea() Color.gray .frame(width: 300, height: 200) .overlay(alignment: .bottomLeading) { // 透明锚点,同步地球的位置和大小 Color.clear .frame(width: 200, height: 200) .padding() .offset(x: -75, y: 75) .matchedGeometryEffect(id: "globe", in: namespace) } .frame(maxWidth: .infinity, maxHeight: .infinity) } } }
关键修改说明
- 将地球图标移到父视图ZStack中,确保它始终存在,不会被条件渲染销毁
- 子视图中用透明
Color.clear作为锚点,通过matchedGeometryEffect同步地球的位置、大小布局 - 旋转动画直接作用在父视图的地球图标上,切换时累加旋转角度(而非重置为0/360),保证旋转连续平滑
- 用
opacity替代条件渲染控制视图显示,避免视图销毁导致动画中断
如果你坚持使用条件渲染,可以给子视图添加transition(.opacity),但始终保留视图的方式动画连贯性更稳定。
内容的提问来源于stack exchange,提问作者CalebK
相关产品推荐
相关产品推荐

