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

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)
    }
}

关键说明

  1. 滚动偏移监听:通过GeometryReader和自定义偏好键ScrollOffsetKey获取ScrollView的滚动位置,以此计算每个视图与屏幕中心的距离。
  2. 动态缩放逻辑:根据视图到屏幕中心的距离,线性调整缩放比例——距离为0(完全居中)时使用maxScale,距离达到相邻视图中心间距时使用minScale,中间值平滑过渡。
  3. 首尾视图适配:给HStack添加左右内边距,确保第一个和最后一个海报也能滚动到屏幕中心位置,避免边缘视图无法完全放大。
  4. 分页优化(可选):如果需要类似PageTabView的自动居中分页效果,可以给ScrollView添加.scrollTargetBehavior(.viewAligned)和.scrollTargetLayout(),并通过滚动偏移计算当前选中的索引,实现滚动后自动对齐到中心视图。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 21:05:09