You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI实现LazyHStack单元格到详情页的Instagram式转场动画

SwiftUI实现Instagram式故事转场(源视图保持不动)

你的问题出在:当前代码通过if-else切换主视图和详情页,导致选中的源单元格被从视图层级中移除,matchedGeometryEffect会将源视图的位置过渡到详情页,所以才会出现源单元格移动的现象。要让源视图保持原位,核心是保留源视图的存在,仅在详情页显示时隐藏选中的源单元格,同时让详情页的视图基于源单元格的位置做过渡动画。

以下是修改后的完整代码:

struct ContentView: View {
    @Namespace private var animationNamespace
    @State private var selectedIndex: Int? = nil
    
    var body: some View {
        ZStack {
            // 主视图始终保留在层级中
            ScrollView(.horizontal) {
                LazyHStack(spacing: 10) {
                    ForEach(0..<5) { index in
                        RoundedRectangle(cornerRadius: 20)
                            .fill(Color.blue)
                            .frame(width: 80, height: 80)
                            .matchedGeometryEffect(id: "container\(index)", in: animationNamespace)
                            // 选中时隐藏源单元格,但不移除
                            .opacity(selectedIndex == index ? 0 : 1)
                            .onTapGesture {
                                withAnimation(.easeInOut(duration: 0.5)) {
                                    selectedIndex = index
                                }
                            }
                    }
                }
                .padding()
            }
            
            // 详情页仅在选中时显示,覆盖在主视图上方
            if let index = selectedIndex {
                DetailView(namespace: animationNamespace, selectedIndex: index) {
                    withAnimation(.easeInOut(duration: 0.5)) {
                        selectedIndex = nil
                    }
                }
            }
        }
    }
}

struct DetailView: View {
    var namespace: Namespace.ID
    let selectedIndex: Int
    let closeClosure: (() -> Void)
    
    var body: some View {
        RoundedRectangle(cornerRadius: 20)
            .fill(Color.purple)
            .matchedGeometryEffect(id: "container\(selectedIndex)", in: namespace)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .edgesIgnoringSafeArea(.all)
            .onTapGesture {
                closeClosure()
            }
    }
}

#Preview {
    ContentView()
}

关键修改点说明:

  • 保留主视图层级:移除了if-else切换逻辑,让ScrollView始终存在于ZStack中,避免源视图被移除。
  • 隐藏选中的源单元格:通过opacity(selectedIndex == index ? 0 : 1)让选中的单元格透明,这样它依然占据原位置,但不可见,matchedGeometryEffect会以它的位置作为动画起点。
  • 简化详情页布局:移除了多余的VStack,让详情页的矩形直接填充全屏,确保动画过渡到正确的结束状态。

这样修改后,点击单元格时,详情页会从源单元格的位置平滑放大到全屏,关闭时反向缩小,而源列表中的其他单元格和选中的单元格(隐藏但原位保留)都不会移动,完全符合Instagram故事的转场效果。

内容的提问来源于stack exchange,提问作者Xys

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 20:34:55