SwiftUI自定义图片滑块视图:实现选中视图放大的横向滑动效果
实现选中视图放大的横向滑动效果
要实现选中的中间视图尺寸最大、相邻视图缩小的横向滑动效果,不建议用PageTabViewStyle,因为它的布局逻辑是固定的等宽分页,难以自定义缩放样式。更灵活的方案是用ScrollView结合GeometryReader来动态计算每个视图的缩放比例,以下是完整实现:
核心实现代码
import SwiftUI struct ZoomableMovieScrollView: View { // 替换为你的电影海报数据模型 let movies: [Movie] @State private var scrollOffset: CGFloat = 0 // 可配置参数 private let baseItemWidth: CGFloat = 200 private let itemAspectRatio: CGFloat = 1.5 // 海报常用比例 2:3 private let maxScale: CGFloat = 1.2 // 选中视图的最大缩放比例 private let minScale: CGFloat = 0.8 // 非选中视图的最小缩放比例 private let itemSpacing: CGFloat = 20 var body: some View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: itemSpacing) { ForEach(movies) { movie in // 替换为你的海报ImageView Image(movie.posterName) .resizable() .aspectRatio(itemAspectRatio, contentMode: .fill) .frame(width: baseItemWidth) .cornerRadius(12) .scaleEffect(scale(for: movie)) .clipped() } } // 左右内边距,让首尾视图也能滚动到屏幕中心 .padding(.horizontal, (UIScreen.main.bounds.width - baseItemWidth) / 2) // 监听滚动偏移量 .background(GeometryReader { proxy in Color.clear .preference(key: ScrollOffsetKey.self, value: proxy.frame(in: .global).minX) }) } .onPreferenceChange(ScrollOffsetKey.self) { offset in scrollOffset = offset } .frame(height: baseItemWidth * itemAspectRatio * maxScale) } // 根据视图与屏幕中心的距离计算缩放比例 private func scale(for movie: Movie) -> CGFloat { guard let index = movies.firstIndex(of: movie) else { return 1.0 } // 计算当前视图的中心X坐标(相对于ScrollView的原点) let itemCenterX = CGFloat(index) * (baseItemWidth + itemSpacing) + baseItemWidth / 2 // 屏幕中心X坐标 let screenCenterX = UIScreen.main.bounds.width / 2 // 计算视图中心与屏幕中心的距离 let distanceToCenter = abs(itemCenterX + scrollOffset - screenCenterX) // 相邻视图的最大距离(中心到中心) let maxRelevantDistance = baseItemWidth + itemSpacing // 距离越近,缩放比例越大;超过最大距离后保持最小缩放 let calculatedScale = max(minScale, maxScale - (distanceToCenter / maxRelevantDistance) * (maxScale - minScale)) return calculatedScale } } // 偏好键:用于传递滚动偏移量 struct ScrollOffsetKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() } } // 示例电影数据模型 struct Movie: Identifiable, Equatable { let id = UUID() let posterName: String } // 使用示例 struct ContentView: View { let mockMovies = [ Movie(posterName: "poster1"), Movie(posterName: "poster2"), Movie(posterName: "poster3"), Movie(posterName: "poster4"), Movie(posterName: "poster5") ] var body: some View { ZoomableMovieScrollView(movies: mockMovies) } }
关键说明
- 滚动偏移监听:通过
GeometryReader和自定义偏好键ScrollOffsetKey获取ScrollView的滚动位置,以此计算每个视图与屏幕中心的距离。 - 动态缩放逻辑:根据视图到屏幕中心的距离,线性调整缩放比例——距离为0(完全居中)时使用
maxScale,距离达到相邻视图中心间距时使用minScale,中间值平滑过渡。 - 首尾视图适配:给HStack添加左右内边距,确保第一个和最后一个海报也能滚动到屏幕中心位置,避免边缘视图无法完全放大。
- 分页优化(可选):如果需要类似PageTabView的自动居中分页效果,可以给ScrollView添加
.scrollTargetBehavior(.viewAligned)和.scrollTargetLayout(),并通过滚动偏移计算当前选中的索引,实现滚动后自动对齐到中心视图。
内容的提问来源于stack exchange,提问作者Yousif Ismat
相关产品推荐
相关产品推荐

