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

SwiftUI中ScrollView滚动时如何动态更新视图样式?

实现横向ScrollView滚动时中间元素放大及样式优化

要实现你需要的效果,我们需要跟踪横向ScrollView的滚动位置,判断哪个星座元素处于屏幕中间,然后动态应用缩放、边框等样式。以下是修改后的完整代码:

struct SULoveCompatibilityInputView: View, HoroscopesProtocol {
    @Environment(\.presentationMode) var presentationMode
    var viewModel = LoveCompatibilityViewModel()
    @State var horoscopeInfo: [LoveCompatibilityHoroscopeData] = []
    let screenWidth = UIScreen.main.bounds.width
    let itemWidth: CGFloat = 110
    let spacing: CGFloat = ((UIScreen.main.bounds.width/2) - 125)
    // 跟踪当前处于中间的元素ID
    @State private var centeredItemId: Int?
    
    var body: some View {
        NavigationView {
            ZStack{
                Color(ThemeManager.pageBackgroundColor()).ignoresSafeArea()
                ScrollView{
                    VStack{
                        Spacer().frame(height: 20)
                        UserHoroscopeInfoView()
                        Spacer().frame(height: 45)
                        FriendHoroscopeInfoView().padding()
                        Spacer().frame(height: 5)
                        
                        ScrollViewReader { proxy in
                            ScrollView(.horizontal, showsIndicators: false){
                                HStack(spacing: spacing){
                                    Spacer().frame(width: 70)
                                    ForEach(horoscopeInfo, id: \.id) { signData in
                                        HoroscopeItemView(signData: signData)
                                            .frame(width: itemWidth)
                                            // 监听元素位置,判断是否处于中间区域
                                            .background(
                                                GeometryReader { geo in
                                                    Color.clear
                                                        .onAppear { checkIfCentered(geo: geo, itemId: signData.id) }
                                                        .onChange(of: proxy.scrollOffset) { _ in
                                                            checkIfCentered(geo: geo, itemId: signData.id)
                                                        }
                                                }
                                            )
                                    }
                                    Spacer().frame(width: 70)
                                }
                                .padding(.top, 10)
                                .padding(.bottom, 15)
                            }
                        }
                        .onAppear{
                            viewModel.horoscopes = self
                            viewModel.getHoroscopes()
                        }
                    }
                }
                .navigationTitle("horoscopeCompatibilityTitle".localized)
                .navigationBarItems(leading: Button(action: {
                    presentationMode.wrappedValue.dismiss()
                }) {
                    Image(systemName: "chevron.left")
                })
                // 传递中间元素ID到子视图
                .environment(\.centeredItemId, centeredItemId)
            }
        }
    }
    
    // 判断元素是否处于屏幕中间区域
    private func checkIfCentered(geo: GeometryProxy, itemId: Int) {
        let itemCenterX = geo.frame(in: .global).midX
        let screenCenterX = screenWidth / 2
        // 中间区域阈值,可按需调整
        let threshold: CGFloat = 50
        
        if abs(itemCenterX - screenCenterX) < threshold {
            centeredItemId = itemId
        } else if centeredItemId == itemId {
            centeredItemId = nil
        }
    }
    
    func fillFriendSignScroll(data: [LoveCompatibilityHoroscopeData]) {
        horoscopeInfo = data
    }
    
    // 抽离星座元素为独立View,便于样式控制
    private struct HoroscopeItemView: View {
        let signData: LoveCompatibilityHoroscopeData
        @Environment(\.centeredItemId) private var centeredItemId
        
        private var isCentered: Bool {
            centeredItemId == signData.id
        }
        
        var body: some View {
            VStack{
                Spacer().frame(height: 20)
                ZStack(alignment: .topTrailing){
                    Image(signData.signIcon ?? "virgo")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 110, height: 110)
                        // 中间元素放大至1.2倍,添加平滑动画
                        .scaleEffect(isCentered ? 1.2 : 1.0)
                        .animation(.easeInOut(duration: 0.2), value: isCentered)
                        // 添加带圆角的边框,中间元素边框加粗
                        .overlay(
                            RoundedRectangle(cornerRadius: 8)
                                .stroke(Color(ThemeManager.accentColor()), lineWidth: isCentered ? 2 : 1)
                                .animation(.easeInOut(duration: 0.2), value: isCentered)
                        )
                    
                    Image("popupTik")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 20, height: 20)
                        // 添加阴影提升可见性
                        .shadow(color: .black.opacity(0.3), radius: 2, x: 0, y: 1)
                        // 中间元素的勾选图标进一步放大
                        .scaleEffect(isCentered ? 1.1 : 1.0)
                        .animation(.easeInOut(duration: 0.2), value: isCentered)
                        // 微调位置避免被放大的图片遮挡
                        .offset(x: isCentered ? 5 : 0, y: isCentered ? -5 : 0)
                }
                
                Spacer().frame(height: 20)
                Text(signData.signName ?? "Burç")
                    .font(.system(size: 16).bold())
                    .foregroundColor(Color(ThemeManager.textColor()))
                Spacer().frame(height: 5)
                Text(signData.signDateStart ?? "")
                    .font(.system(size: 14, weight: .medium))
                    .foregroundColor(Color(ThemeManager.textColor()))
                Spacer().frame(height: 2)
                Text(signData.signDateEnd ?? "") // 修复原代码重复显示起始日期的问题
                    .font(.system(size: 14, weight: .medium))
                    .foregroundColor(Color(ThemeManager.textColor()))
            }
        }
    }
}

// 定义环境变量传递中间元素ID
private struct CenteredItemIdKey: EnvironmentKey {
    static let defaultValue: Int? = nil
}

extension EnvironmentValues {
    var centeredItemId: Int? {
        get { self[CenteredItemIdKey.self] }
        set { self[CenteredItemIdKey.self] = newValue }
    }
}

关键修改说明:

  • 中间元素识别:通过GeometryReader监听每个元素的全局位置,对比屏幕中心判断是否处于中间区域,用centeredItemId记录当前中间元素
  • 动态缩放效果:给图片添加scaleEffect,中间元素放大至1.2倍,配合动画实现平滑过渡
  • 边框样式优化:用overlay添加圆角边框,中间元素边框加粗,增强视觉区分度
  • 勾选图标增强:增大图标尺寸、添加阴影,中间时进一步放大并微调位置,提升可见性
  • 代码重构:将星座元素抽离为独立View,通过环境变量传递状态,代码更清晰易维护
  • 细节修复:修正原代码中重复显示起始日期的错误,改为显示结束日期

内容的提问来源于stack exchange,提问作者onur.altintas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 03:17:09