import SwiftUI struct FilmAnimation: View { // 设备尺寸 private let deviceWidth = UIScreen.main.bounds.width private let deviceHeight = UIScreen.main.bounds.height // 动画状态控制 @State private var animationProgress: CGFloat = 0.0 // 0-1总进度 @State private var isAnimating: Bool = false @State private var animationComplete: Bool = false // 胶卷数据 private let reelImages: [[String]] = [ (0..<300).map { "film1-\($0+1)" }, // 上方胶卷 (0..<350).map { "film2-\($0+1)" }, // 中间胶卷 (0..<300).map { "film3-\($0+1)" } // 下方胶卷 ] // 胶卷参数 private let frameWidth: CGFloat = 90 private let frameHeight: CGFloat = 130 private let frameSpacing: CGFloat = 12 // 动画阶段时间参数 private let accelerationDuration: Double = 5.0 // 0-5s加速 private let constantSpeedDuration: Double = 1.0 // 5-6s匀速 private let scaleStartDuration: Double = 1.0 // 6-7s共同放大 private let scaleFinishDuration: Double = 1.0 // 7-8s仅中间胶卷放大 private var totalDuration: Double { accelerationDuration + constantSpeedDuration + scaleStartDuration + scaleFinishDuration } // 各阶段进度阈值 private var accelerationEnd: CGFloat { accelerationDuration / totalDuration } private var constantSpeedEnd: CGFloat { (accelerationDuration + constantSpeedDuration) / totalDuration } private var scaleStartEnd: CGFloat { (accelerationDuration + constantSpeedDuration + scaleStartDuration) / totalDuration } // 布局与运动参数(核心:对称倾斜角度) private let tiltAngle: Double = 10 // 基础倾斜角度 private let upperTilt: Double = -10 // 上方胶卷:左高右低(负角度) private let lowerTilt: Double = 10 // 下方胶卷:左低右高(正角度) private let verticalSpacing: CGFloat = 200 // 上下胶卷垂直间距 private let finalScale: CGFloat = 4.5 // 移动距离参数 private let maxTiltedReelMovement: CGFloat = 3500 // 倾斜胶卷最大移动距离 private let maxMiddleReelMovement: CGFloat = -3000 // 中间胶卷最大移动距离 var body: some View { // 固定背景 Color(red: 0.08, green: 0.08, blue: 0.08) .edgesIgnoringSafeArea(.all) .overlay( ZStack { // 上方倾斜胶卷(左高右低,向右移动) if showTiltedReels { FilmReelView(images: reelImages[0]) .rotationEffect(Angle(degrees: upperTilt)) // 左高右低 .offset(x: upperReelXPosition, y: -verticalSpacing/2) .scaleEffect(tiltedScale) .opacity(tiltedOpacity) .zIndex(1) } // 下方倾斜胶卷(左低右高,向右移动) if showTiltedReels { FilmReelView(images: reelImages[2]) .rotationEffect(Angle(degrees: lowerTilt)) // 左低右高 .offset(x: lowerReelXPosition, y: verticalSpacing/2) .scaleEffect(tiltedScale) .opacity(tiltedOpacity) .zIndex(1) } // 中间胶卷(垂直,向左移动) FilmReelView(images: reelImages[1]) .offset(x: middleReelXPosition, y: 0) .scaleEffect(middleScale) .opacity(1.0) .zIndex(2) .edgesIgnoringSafeArea(.all) } ) .onAppear { startAnimation() } } // MARK: - 动画逻辑 private func startAnimation() { guard !isAnimating && !animationComplete else { return } isAnimating = true withAnimation(Animation.easeInOut(duration: totalDuration)) { animationProgress = 1.0 } DispatchQueue.main.asyncAfter(deadline: .now() + totalDuration) { isAnimating = false animationComplete = true } } // MARK: - 位置计算(确保向右移动) // 上方倾斜胶卷X位置 private var upperReelXPosition: CGFloat { let startPosition: CGFloat = -deviceWidth * 1.2 // 左侧屏幕外起始 return startPosition + (maxTiltedReelMovement * movementProgress) } // 下方倾斜胶卷X位置 private var lowerReelXPosition: CGFloat { let startPosition: CGFloat = -deviceWidth * 0.8 // 稍右于上方胶卷起始 return startPosition + (maxTiltedReelMovement * movementProgress) } // 中间胶卷X位置 private var middleReelXPosition: CGFloat { let startPosition: CGFloat = deviceWidth * 0.3 return startPosition + (maxMiddleReelMovement * movementProgress) } // 移动进度(0-1) private var movementProgress: CGFloat { if animationProgress < constantSpeedEnd { return animationProgress / constantSpeedEnd } else { return 1.0 // 6秒后停止移动 } } // MARK: - 缩放与显示控制 // 中间胶卷缩放 private var middleScale: CGFloat { guard animationProgress >= constantSpeedEnd else { return 1.0 } let scalePhaseProgress = (animationProgress - constantSpeedEnd) / (1.0 - constantSpeedEnd) return 1.0 + (finalScale - 1.0) * scalePhaseProgress } // 倾斜胶卷缩放 private var tiltedScale: CGFloat { guard animationProgress >= constantSpeedEnd, animationProgress < scaleStartEnd else { return 1.0 } let scalePhaseProgress = (animationProgress - constantSpeedEnd) / (scaleStartEnd - constantSpeedEnd) return 1.0 + (finalScale * 0.6 - 1.0) * scalePhaseProgress } // 倾斜胶卷透明度 private var tiltedOpacity: Double { guard animationProgress >= constantSpeedEnd, animationProgress < scaleStartEnd else { return 0.8 } let fadeProgress = (animationProgress - constantSpeedEnd) / (scaleStartEnd - constantSpeedEnd) return 0.8 * (1.0 - fadeProgress) } // 控制倾斜胶卷显示 private var showTiltedReels: Bool { animationProgress < scaleStartEnd } } // MARK: - 胶卷组件 struct FilmReelView: View { let images: [String] var body: some View { HStack(spacing: 12) { ForEach(images.indices, id: \.self) { index in FilmFrameView(imageName: images[index]) } } } } struct FilmFrameView: View { let imageName: String var body: some View { ZStack { // 胶卷边框 RoundedRectangle(cornerRadius: 4) .stroke(Color.gray, lineWidth: 2) .background(Color(red: 0.15, green: 0.15, blue: 0.15)) // 帧内容 Rectangle() .fill(gradientColor) .cornerRadius(2) .padding(2) // 帧标识 Text(imageName) .foregroundColor(.white) .font(.caption2) } .frame(width: 90, height: 130) // 胶卷孔洞 .overlay( HStack { VStack(spacing: 6) { ForEach(0..<6) { _ in Circle() .frame(width: 6, height: 6) .foregroundColor(.gray) } } Spacer() VStack(spacing: 6) { ForEach(0..<6) { _ in Circle() .frame(width: 6, height: 6) .foregroundColor(.gray) } } } ) } private var gradientColor: LinearGradient { if imageName.hasPrefix("film1") { return LinearGradient(gradient: Gradient(colors: [.blue, .indigo]), startPoint: .topLeading, endPoint: .bottomTrailing) } else if imageName.hasPrefix("film2") { return LinearGradient(gradient: Gradient(colors: [.yellow, .orange]), startPoint: .topLeading, endPoint: .bottomTrailing) } else { return LinearGradient(gradient: Gradient(colors: [.purple, .pink]), startPoint: .topLeading, endPoint: .bottomTrailing) } } } // 预览 struct FilmAnimation_Previews: PreviewProvider { static var previews: some View { FilmAnimation() } }