如何在SwiftUI中实现对角线Carousel并让中间元素突出?
SwiftUI 对角线Carousel实现方案
ScrollView原生仅支持单轴滚动,无法满足对角线滚动需求,你可以通过自定义手势监听+位置动态计算的方式实现对应效果,具体实现逻辑如下:
- 拖拽手势处理
使用DragGesture()监听用户拖拽操作,将x、y方向的偏移量按对角线斜率合并为总滚动进度,绑定到@State修饰的滚动偏移变量。拖拽结束后添加惯性动效和停靠逻辑,保证元素会自动对齐到中间展示位。 - 元素布局计算
用ZStack叠加所有Carousel元素,每个元素的x、y偏移都按固定比例和当前滚动进度计算,实现整体沿对角线滚动的效果。你可以通过调整x、y的偏移比例自定义对角线的倾斜角度。 - 差异化样式配置
计算每个元素和当前居中位置的偏移差值,根据差值动态调整元素样式:- 差值小于0.5(中间元素):放大1.1-1.3倍,设置你需要的普通圆角,zIndex调整到最高,添加更深的阴影突出层级
- 差值大于等于1(两侧元素):缩小到0.7-0.9倍,圆角设置为元素宽度的1/2实现圆形效果,适当降低透明度,zIndex调低避免遮挡中间元素
- 性能优化
当元素数量大于5个时,仅渲染当前可视范围内的3-5个元素,超出范围的元素直接隐藏,减少不必要的绘制开销。
核心实现代码参考:
import SwiftUI struct DiagonalCarousel: View { // 单个元素基础尺寸 private let itemSize: CGSize = .init(width: 100, height: 100) // 对角线步长,调整x、y数值可以修改对角线斜率 private let step: (x: CGFloat, y: CGFloat) = (60, 40) private let items = Array(0..<10) @State private var scrollOffset: CGFloat = 0 @State private var currentIndex: Int = 0 var body: some View { ZStack { ForEach(items, id: \.self) { index in // 计算元素相对中间位置的偏移量 let relativeOffset = CGFloat(index) - scrollOffset / (step.x + step.y) * 2 let absOffset = abs(relativeOffset) RoundedRectangle(cornerRadius: absOffset < 0.5 ? 16 : itemSize.width / 2) .fill(Color.blue.opacity(0.8)) .frame(width: itemSize.width, height: itemSize.height) .offset( x: relativeOffset * step.x, y: relativeOffset * step.y ) .scaleEffect(absOffset < 0.5 ? 1.2 : 0.8) .zIndex(10 - absOffset) .opacity(absOffset > 2 ? 0 : (absOffset < 0.5 ? 1 : 0.7)) .shadow(radius: absOffset < 0.5 ? 10 : 2) } } .gesture( DragGesture() .onChanged { value in // 合并x、y偏移计算总滚动量 let totalDrag = value.translation.width + value.translation.height scrollOffset = -totalDrag } .onEnded { value in // 拖拽结束后计算停靠的目标位置 let predictDrag = value.predictedEndTranslation.width + value.predictedEndTranslation.height let targetOffset = -predictDrag let targetIndex = round(targetOffset / (step.x + step.y) * 2) withAnimation(.easeOut(duration: 0.3)) { scrollOffset = targetIndex * (step.x + step.y) / 2 currentIndex = max(0, min(items.count - 1, Int(targetIndex))) } } ) } }
如果需要实现无限滚动效果,只需要在滚动到数据源边界时重置scrollOffset数值,同时循环处理元素索引即可,不需要修改核心逻辑。
内容的提问来源于stack exchange,提问作者Red Lion
相关产品推荐
相关产品推荐

