如何实现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
相关产品推荐
相关产品推荐

