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

如何实现LazyVGrid单元格滚动释放后自动居中吸附(类分页效果)?

实现LazyVGrid的分页吸附效果

要实现你需要的滚动吸附效果,可以结合ScrollViewReader、GeometryReader和手势监听来完成,核心是在滚动结束后判断当前需要居中的单元格,再自动滚动定位。

修改后的完整代码

struct ContentView: View {
    private var columns: [GridItem] = [
        GridItem(.adaptive(minimum: .infinity, maximum: .infinity), spacing: 0)
    ]
    private var items = Array(stride(from: 0, to: 100, by: 1))
    
    // 存储每个单元格的全局位置信息
    @State private var cellFrames: [CGRect] = []
    // 获取屏幕尺寸(替代UIScreen,更适配SwiftUI布局)
    @State private var screenSize = CGSize.zero
    
    var body: some View {
        GeometryReader { screenGeo in
            ScrollViewReader { proxy in
                ScrollView {
                    VStack {
                        LazyVGrid(
                            columns: columns,
                            alignment: .center,
                            spacing: 10,
                            pinnedViews: []
                        ) {
                            ForEach(0..<items.count, id: \.self) { i in
                                Text("\(i)")
                                    .frame(width: screenSize.width - 20, height: screenSize.height - 150)
                                    .background(Color.red)
                                    .foregroundColor(.white)
                                    .id(i)
                                    // 获取每个单元格的全局位置
                                    .background(
                                        GeometryReader { geo in
                                            Color.clear
                                                .onAppear {
                                                    if cellFrames.count <= i {
                                                        cellFrames.append(geo.frame(in: .global))
                                                    } else {
                                                        cellFrames[i] = geo.frame(in: .global)
                                                    }
                                                }
                                                .onChange(of: screenSize) { _ in
                                                    cellFrames[i] = geo.frame(in: .global)
                                                }
                                        }
                                    )
                            }
                        }
                    }
                }
                // 监听拖动手势结束事件
                .gesture(
                    DragGesture()
                        .onEnded { _ in
                            snapToNearestCell(proxy: proxy)
                        }
                )
            }
            .padding()
            .onAppear {
                screenSize = screenGeo.size
            }
        }
    }
    
    private func snapToNearestCell(proxy: ScrollViewProxy) {
        let screenCenterY = screenSize.height / 2
        
        // 找到需要居中的单元格索引
        var targetIndex = 0
        for (index, frame) in cellFrames.enumerated() {
            let cellTop = frame.minY
            let cellBottom = frame.maxY
            
            // 判断条件:向上滚动时顶部过中心,或向下滚动时底部过中心
            if cellTop < screenCenterY && cellBottom > screenCenterY {
                // 当前已经在中心附近,直接用这个
                targetIndex = index
                break
            } else if cellTop > screenCenterY {
                // 下一个单元格顶部超过中心,定位到上一个
                targetIndex = max(index - 1, 0)
                break
            } else if cellBottom < screenCenterY {
                // 当前单元格底部低于中心,定位到下一个
                targetIndex = min(index + 1, items.count - 1)
            }
        }
        
        // 滚动到目标单元格,锚点设为.center实现居中
        withAnimation {
            proxy.scrollTo(targetIndex, anchor: .center)
        }
    }
}

关键实现细节

  • 获取单元格位置:给每个单元格添加GeometryReader,在onAppear和尺寸变化时记录全局坐标,确保能准确判断单元格位置。
  • 手势监听:通过DragGesture().onEnded捕捉滚动结束的时机,触发吸附逻辑。
  • 吸附逻辑:计算屏幕垂直中心,遍历单元格位置,判断哪个单元格需要居中,同时处理边界情况避免索引越界。
  • 滚动定位:用scrollTo(_:anchor:)并指定.center锚点,让目标单元格自动居中,加上withAnimation让滚动过渡更平滑。

注意事项

  • 替换了UIScreen.main.bounds,改用GeometryReader获取屏幕尺寸,更符合SwiftUI的布局逻辑,适配不同设备和横竖屏切换。
  • 单元格位置数组需要在尺寸变化时更新,避免布局变化后定位错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 20:51:33