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

SwiftUI实现ScrollView项左边缘展开动画的问题求助

复刻谷歌网页/桌面端项悬停展开动画的问题求助

目标动画效果

谷歌目标展开动画效果

当前实现效果

当前实现的展开动画效果

遇到的问题

  • 动画从中心而非左边缘展开,且呈现闪烁而非平滑展开效果
  • 被点击项右侧的项标题与当前选中项重叠
  • 图片切换仅为直接替换,而非谷歌动画中的淡入淡出效果

我的代码

struct CarouselItem: Identifiable, Equatable {
    private(set) var id: UUID = .init()
    var title: String
    var year: String
    var runtime: String
    var certification: String
    var posterImage: String
    var stillImage: String
}

struct CarousalItemView: View {
    @State var item: CarouselItem
    @Binding var selectedItem: CarouselItem?
    
    var body: some View {
        if item.id == selectedItem?.id {
            selectedItemView()
        } else {
            unselectedItemView()
        }
    }
    
    @ViewBuilder func selectedItemView() -> some View {
        VStack(alignment: .leading) {
            ZStack {
                Image(item.stillImage)
                    .resizable()
                    .scaledToFill()
                    .frame(width: 250, height: 200)
                    .clipShape(RoundedRectangle(cornerRadius: 10))
                    .overlay(alignment: .leading) {
                        VStack(alignment: .leading, spacing: 15) {
                            Spacer()
                            Text(item.title)
                                .font(.title2)
                            HStack {
                                Text("CBFC: " + item.certification)
                                    .padding(.horizontal, 5)
                                    .overlay {
                                        Rectangle()
                                            .stroke(.white, lineWidth: 1)
                                    }
                                Text(item.runtime)
                                Text(item.year)
                            }
                            .shadow(color: .gray, radius: 5, y: 5)
                            Button {
                                
                            } label: {
                                Text("Trailer")
                            }
                            .buttonStyle(.borderedProminent)
                            .tint(.gray)
                            .padding(.bottom, 15)
                        }
                        .padding(.horizontal, 10)
                    }
            }
            Text(item.title)
                .frame(height: 50)
        }
    }
    
    @ViewBuilder func unselectedItemView() -> some View {
        VStack(alignment: .leading) {
            Image(item.posterImage)
                .resizable()
                .scaledToFill()
                .frame(width: 150, height: 200)
                .clipShape(RoundedRectangle(cornerRadius: 10))
            Text(item.title)
                .frame(height: 50)
        }
        .foregroundStyle(.white)
    }
}

struct ContentView: View {
    var items: [CarouselItem] = [
        .init(title: "Oppenheimer", year: "2023", runtime: "3h 1m", certification: "UA", posterImage: "poster1", stillImage: "still1"),
        .init(title: "Barbie", year: "2023", runtime: "1h 54m", certification: "PG", posterImage: "poster2", stillImage: "still2"),
        .init(title: "The Martian", year: "2015", runtime: "2h 24m", certification: "UA", posterImage: "poster3", stillImage: "still3"),
        .init(title: "The Shawshank Redemption", year: "1994", runtime: "2h 22m", certification: "U", posterImage: "poster4", stillImage: "still4"),
        .init(title: "The Godfather", year: "1972", runtime: "2h 55m", certification: "UA", posterImage: "poster5", stillImage: "still5"),
        .init(title: "The Dark Knight", year: "2008", runtime: "2h 32m", certification: "UA", posterImage: "poster6", stillImage: "still6")
    ]
    @State var selectedItem: CarouselItem?
    var body: some View {
        VStack {
            HStack {
                Text("Popular films")
                    .font(.title2.bold())
                Spacer()
            }
            
            // MARK: Carousal
            ScrollView(.horizontal) {
                LazyHStack(spacing: 10) {
                    ForEach(items, id: \.id) { item in
                        CarousalItemView(item: item, selectedItem: $selectedItem)
                            .frame(width: selectedItem?.id != item.id ? 150 : 250, height: 250)
                            .clipShape(RoundedRectangle(cornerRadius: 10))
                            .contentShape(Rectangle())
                            .onTapGesture {
                                withAnimation {
                                    if selectedItem?.id == item.id {
                                        selectedItem = nil
                                    } else {
                                        selectedItem = item
                                    }
                                }
                            }
                    }
                }
            }
            .frame(height: 250)
            .scrollIndicators(.hidden)
            .clipped()

            Spacer()
        }
        .padding()
        .background {
            Color(.darkGray).ignoresSafeArea()
        }
        .foregroundStyle(.white)
    }
}

恳请各位提供帮助。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 16:53:12