import SwiftUI struct FilmAnimation5: 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..<150).map { "film1-\($0+1)" }, // 上方倾斜胶卷 (0..<180).map { "film2-\($0+1)" }, // 中间胶卷 (0..<150).map { "film3-\($0+1)" } // 下方倾斜胶卷 ] // 胶卷参数 private let frameWidth: CGFloat = 90 private let frameHeight: CGFloat = 130 private let totalDistance: CGFloat = 1800 // 总移动距离 // 动画阶段时间参数(核心调整) private let accelerationDuration: Double = 5.0 // 0-5s加速 private let constantSpeedDuration: Double = 1.0 // 5-6s匀速移动 private let scaleDuration: Double = 2.0 // 6-8s共同放大 private var totalDuration: Double { accelerationDuration + constantSpeedDuration + scaleDuration } // 各阶段进度阈值 private var accelerationEnd: CGFloat { accelerationDuration / totalDuration } private var constantSpeedEnd: CGFloat { (accelerationDuration + constantSpeedDuration) / totalDuration } // 对称倾斜参数 private let symmetricTiltAngle: Double = 10 // 上下胶卷对称倾斜角度 private let verticalOffset: CGFloat = 120 // 上下胶卷垂直距离(对称) private let finalScale: CGFloat = 4.0 // 最终放大倍数 var body: some View { ZStack { // 深色背景 Color(red: 0.08, green: 0.08, blue: 0.08) .edgesIgnoringSafeArea(.all) // 上方倾斜胶卷(向右移动) FilmReelView5(images: reelImages[0]) .rotationEffect(Angle(degrees: -symmetricTiltAngle)) .offset(x: topReelPosition, y: -verticalOffset) .scaleEffect(currentScale) .opacity(upperLowerOpacity) .zIndex(2) // 下方倾斜胶卷(向右移动) FilmReelView5(images: reelImages[2]) .rotationEffect(Angle(degrees: symmetricTiltAngle)) .offset(x: bottomReelPosition, y: verticalOffset) .scaleEffect(currentScale) .opacity(upperLowerOpacity) .zIndex(2) // 中间胶卷(向左移动,最终保留) FilmReelView5(images: reelImages[1]) .offset(x: middleReelPosition, y: 0) .scaleEffect(currentScale) .opacity(1.0) // 始终不透明 .zIndex(1) .edgesIgnoringSafeArea(.all) } .onAppear { startAnimation() } } // MARK: - 动画逻辑 private func startAnimation() { guard !isAnimating && !animationComplete else { return } isAnimating = true // 分阶段动画曲线:先加速后匀速 withAnimation(Animation.timingCurve(0.3, 0.0, 0.7, 1.0, duration: totalDuration)) { animationProgress = 1.0 } // 动画结束标记 DispatchQueue.main.asyncAfter(deadline: .now() + totalDuration) { isAnimating = false animationComplete = true } } // MARK: - 动画计算 // 共同放大比例(6s后开始放大) private var currentScale: CGFloat { guard animationProgress >= constantSpeedEnd else { return 1.0 // 前6s保持原尺寸 } // 放大阶段相对进度(0-1) let scalePhaseProgress = (animationProgress - constantSpeedEnd) / (1.0 - constantSpeedEnd) return 1.0 + (finalScale - 1.0) * scalePhaseProgress } // 上下胶卷透明度(放大阶段逐渐隐藏) private var upperLowerOpacity: Double { guard animationProgress >= constantSpeedEnd else { return 0.8 // 前6s保持可见 } // 放大阶段同步淡出 let fadeProgress = (animationProgress - constantSpeedEnd) / (1.0 - constantSpeedEnd) return 0.8 * (1.0 - fadeProgress) } // MARK: - 移动速度控制(确保匀速阶段速度一致) private var motionProgress: CGFloat { if animationProgress < accelerationEnd { // 0-5s加速阶段:二次方曲线加速 let t = animationProgress / accelerationEnd return t * t } else { // 5s后匀速阶段:保持最大速度 return 1.0 + (animationProgress - accelerationEnd) * (accelerationEnd / (1.0 - accelerationEnd)) } } // 上方胶卷位置(向右移动) private var topReelPosition: CGFloat { totalDistance * 0.8 * motionProgress } // 中间胶卷位置(向左移动) private var middleReelPosition: CGFloat { -totalDistance * 0.8 * motionProgress // 与上下胶卷速度大小相同,方向相反 } // 下方胶卷位置(向右移动) private var bottomReelPosition: CGFloat { totalDistance * 0.8 * motionProgress // 与上方胶卷速度完全一致,保持对称 } } // MARK: - 胶卷组件 struct FilmReelView5: View { let images: [String] var body: some View { HStack(spacing: 10) { ForEach(images.indices, id: \.self) { index in FilmFrameView5(imageName: images[index]) } } } } struct FilmFrameView5: 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: [.teal, .cyan]), startPoint: .topLeading, endPoint: .bottomTrailing) } } } // 预览 struct FilmAnimation_Previews5: PreviewProvider { static var previews: some View { FilmAnimation5() } }