如何在SwiftUI中实现旋转式matchedGeometryEffect?
实现旋转式MatchedGeometryEffect布局切换
需求说明
- 切换HStack/VStack布局时,实现沿最短路径的旋转过渡
- 过渡要求:
- 文本始终保持直立,仅沿弧线移动
- 灰色背景以红色圆形为中心旋转,而非视图自身中心
- 背景色在过渡全程保持一致,无淡入淡出
原始代码
struct MyView: View { @Namespace private var animationNamespace @State private var isVertical: Bool = false var body: some View { VStack { Spacer() ZStack { if isVertical { VStack { ForEach(0...10, id: \.self) { index in Rectangle() .frame(width: 20.0, height: 20.0) .foregroundColor((index == 0) ? Color.red : Color.black) .overlay(Text(String(describing: index)).foregroundColor(.white)) .matchedGeometryEffect(id: "\(index)", in: animationNamespace) .transition(.scale(scale: 1)) } } } else { HStack { ForEach(0...10, id: \.self) { index in Rectangle() .frame(width: 20.0, height: 20.0) .foregroundColor((index == 0) ? Color.red : Color.black) .overlay(Text(String(describing: index)).foregroundColor(.white)) .matchedGeometryEffect(id: "\(index)", in: animationNamespace) .transition(.scale(scale: 1)) } } } } .bold() .padding() .background(Color.gray) .cornerRadius(10) .animation(.easeInOut(duration: 2.0), value: isVertical) Spacer() Button("Toggle Layout") { isVertical.toggle() } } .padding() } }
解决方案
核心修改思路
- 分离背景层与内容层,避免背景随布局切换产生淡入淡出
- 以红色圆形为锚点设置容器旋转,实现中心旋转效果
- 给文本添加反向旋转,抵消容器旋转,保证文本始终直立
- 保留
matchedGeometryEffect确保视图位置过渡平滑
修改后代码
struct MyView: View { @Namespace private var animationNamespace @State private var isVertical: Bool = false // 自动沿最短路径计算旋转角度(90°/-270°) private var rotationAngle: Angle { isVertical ? .degrees(90) : .degrees(0) } var body: some View { VStack { Spacer() ZStack { // 固定灰色背景,不随布局切换重建 Color.gray .cornerRadius(10) .padding() // 内容容器:应用旋转动画,锚点指向红色圆形中心 ZStack { if isVertical { VStack(spacing: 0) { contentViews } } else { HStack(spacing: 0) { contentViews } } } // 锚点对应HStack中红色圆形的中心位置(总宽度220,红色圆心x坐标10,相对比例1/22) .rotationEffect(rotationAngle, anchor: .init(x: 1/22, y: 0.5)) .animation(.easeInOut(duration: 2.0), value: isVertical) } .bold() Spacer() Button("Toggle Layout") { isVertical.toggle() } } .padding() } // 提取重复视图,简化代码 private var contentViews: some View { ForEach(0...10, id: \.self) { index in Rectangle() .frame(width: 20.0, height: 20.0) .foregroundColor(index == 0 ? .red : .black) .overlay( Text("\(index)") .foregroundColor(.white) // 反向旋转抵消容器旋转,保持文本直立 .rotationEffect(-rotationAngle) ) .matchedGeometryEffect(id: "\(index)", in: animationNamespace) } } }
关键细节说明
- 锚点精准定位:
anchor: .init(x: 1/22, y: 0.5)是根据HStack总宽度(20*11)和红色圆形中心坐标计算出的相对比例,确保旋转中心完全对齐红色圆形 - 文本直立逻辑:文本层添加与容器方向相反的旋转角度,彻底抵消容器旋转带来的文本倾斜
- 背景固化处理:将灰色背景从动态切换的ZStack中移出,作为独立固定层,彻底解决背景淡入淡出问题
内容的提问来源于stack exchange,提问作者swiftPunk
相关产品推荐
相关产品推荐

