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

SwiftUI中如何让ScrollView内图片居中时触发.scaleEffect动画?

SwiftUI横屏水平ScrollView实现中央图片放大动画

要实现滚动时屏幕中央的图片自动放大,核心是动态计算每个图片与屏幕中心的距离,根据距离调整缩放比例,结合SwiftUI的偏好设置和几何读取器就能完成。以下是具体实现方案:

实现步骤

  1. 用PreferenceKey跟踪ScrollView的滚动偏移量,实时获取滚动位置
  2. 为每个图片添加GeometryReader,获取其在全局坐标系中的位置
  3. 计算图片中心与屏幕中心的水平距离,根据距离动态生成缩放系数(距离越近,缩放比例越大)
  4. 给缩放效果添加平滑动画,让过渡更自然

修改后的完整代码

import SwiftUI

// 用于跟踪ScrollView滚动偏移的偏好键
struct ScrollOffsetPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

struct TestView: View {
    // 存储滚动偏移量
    @State private var scrollOffset: CGFloat = 0
    
    var body: some View {
        GeometryReader { screenGeometry in
            let screenCenterX = screenGeometry.size.width / 2
            // 图片基础宽度
            let imageBaseWidth = screenGeometry.size.width / 3
            // 触发缩放变化的最大距离(图片宽度+间距的一半)
            let maxScaleDistance = (imageBaseWidth + 20) / 2
            
            VStack {
                Spacer()
                ScrollView(.horizontal, showsIndicators: false) {
                    ScrollViewReader { proxy in
                        HStack(spacing: 20) {
                            ForEach(1...6, id: \.self) { index in
                                Image("img\(index)")
                                    .resizable()
                                    .aspectRatio(contentMode: .fit)
                                    .frame(width: imageBaseWidth)
                                    .id(index)
                                    // 获取图片的全局位置
                                    .background(
                                        GeometryReader { imageProxy in
                                            Color.clear.preference(
                                                key: ImagePositionPreferenceKey.self,
                                                value: [index: imageProxy.frame(in: .global).midX]
                                            )
                                        }
                                    )
                                    // 根据位置计算缩放比例
                                    .scaleEffect(getScale(for: index, screenCenterX: screenCenterX, maxScaleDistance: maxScaleDistance))
                                    .animation(.easeInOut(duration: 0.2), value: scrollOffset)
                            }
                        }
                    }
                }
                .coordinateSpace(name: "scroll")
                // 跟踪ScrollView的滚动偏移
                .background(
                    GeometryReader { scrollProxy in
                        Color.clear.preference(
                            key: ScrollOffsetPreferenceKey.self,
                            value: scrollProxy.frame(in: .named("scroll")).minX
                        )
                    }
                )
                .onPreferenceChange(ScrollOffsetPreferenceKey.self) { newValue in
                    scrollOffset = newValue
                }
                Spacer()
            }
        }
    }
    
    // 存储每个图片的中心位置
    private struct ImagePositionPreferenceKey: PreferenceKey {
        static var defaultValue: [Int: CGFloat] = [:]
        static func reduce(value: inout [Int: CGFloat], nextValue: () -> [Int: CGFloat]) {
            value.merge(nextValue()) { $1 }
        }
    }
    
    // 计算指定图片的缩放比例
    private func getScale(for index: Int, screenCenterX: CGFloat, maxScaleDistance: CGFloat) -> CGFloat {
        guard let imageCenterX = ImagePositionPreferenceKey.defaultValue[index] else { return 1.0 }
        let distance = abs(imageCenterX - screenCenterX)
        
        // 距离在阈值内时,线性计算缩放比例(1.0到1.2之间)
        if distance < maxScaleDistance {
            return 1.0 + (0.2 * (1 - distance / maxScaleDistance))
        } else {
            return 1.0
        }
    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        TestView()
            .previewInterfaceOrientation(.landscapeLeft)
    }
}

关键代码说明

  • ScrollOffsetPreferenceKey:用来捕获ScrollView的滚动偏移,实时更新scrollOffset变量
  • ImagePositionPreferenceKey:记录每个图片的全局中心位置,为计算距离提供数据
  • getScale方法:根据图片中心与屏幕中心的距离,动态生成缩放比例——距离为0(完全居中)时缩放至1.2,超过阈值时恢复原大小
  • 动画设置:给scaleEffect绑定scrollOffset作为动画触发值,让缩放过渡更平滑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 01:24:29