import SwiftUI struct FilmAnimation1: 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 frameSpacing: CGFloat = 10 private let totalDistance: CGFloat = 2000 // 总移动距离 // 动画时间参数 private let accelerationDuration: Double = 5.0 // 加速阶段时长(0-5s) private let constantSpeedDuration: Double = 6.0 // 匀速+放大阶段时长(5-11s) private var totalDuration: Double { accelerationDuration + constantSpeedDuration } private var scaleStartProgress: CGFloat { accelerationDuration / totalDuration } private let finalScale: CGFloat = 3.0 // 展示完整胶片的缩放比例 // 对称布局核心参数(重点调整) private let symmetricTiltAngle: Double = 8 // 减小倾斜角度,增强对称感 private let verticalOffset: CGFloat = 140 // 减小垂直距离,靠近中间胶卷 private let initialMiddleY: CGFloat = 50 // 中间胶卷初始位置上移,缩短与上下距离 // 上下胶卷与中间胶卷的初始水平偏移(确保视觉对称) private let horizontalOffset: CGFloat = 30 var body: some View { ZStack { // 深色背景 Color(red: 0.08, green: 0.08, blue: 0.08) .edgesIgnoringSafeArea(.all) // 上方倾斜胶卷(左高右低,与中间距离适中) FilmReelView3(images: reelImages[0]) .rotationEffect(Angle(degrees: -symmetricTiltAngle)) .offset(x: topReelPosition - horizontalOffset, y: -verticalOffset) // 水平微调增强对称 .opacity(upperLowerOpacity) .zIndex(1) // 下方倾斜胶卷(左低右高,与中间距离适中) FilmReelView3(images: reelImages[2]) .rotationEffect(Angle(degrees: symmetricTiltAngle)) .offset(x: bottomReelPosition + horizontalOffset, y: verticalOffset) // 水平微调增强对称 .opacity(upperLowerOpacity) .zIndex(1) // 中间胶卷(垂直居中) FilmReelView3(images: reelImages[1]) .offset(x: middleReelPosition, y: middleYPosition) .scaleEffect(currentScale) .position(centerPosition) .zIndex(2) .edgesIgnoringSafeArea(.all) } .onAppear { startAnimation() } } // MARK: - 动画逻辑 private func startAnimation() { guard !isAnimating && !animationComplete else { return } isAnimating = true withAnimation(Animation.timingCurve(0.2, 0.0, 0.8, 1.0, duration: totalDuration)) { animationProgress = 1.0 } DispatchQueue.main.asyncAfter(deadline: .now() + totalDuration) { isAnimating = false animationComplete = true } } // MARK: - 动画计算 private var currentScale: CGFloat { guard animationProgress >= scaleStartProgress else { return 1.0 } let scalePhaseProgress = (animationProgress - scaleStartProgress) / (1.0 - scaleStartProgress) return 1.0 + (finalScale - 1.0) * scalePhaseProgress } // 中间胶卷Y轴位置(微调至更居中) private var middleYPosition: CGFloat { if animationProgress < scaleStartProgress { return initialMiddleY - (initialMiddleY * (animationProgress / scaleStartProgress)) } else { return 0 // 5s后精准居中 } } private var upperLowerOpacity: Double { if animationProgress < scaleStartProgress { return 0.8 } else { let fadeProgress = (animationProgress - scaleStartProgress) / (1.0 - scaleStartProgress) return 0.8 * (1.0 - fadeProgress) } } private var centerPosition: CGPoint { CGPoint(x: deviceWidth / 2, y: deviceHeight / 2) } // MARK: - 位置计算(确保对称运动) private var motionProgress: CGFloat { if animationProgress < scaleStartProgress { let t = animationProgress / scaleStartProgress return t * t // 加速阶段 } else { return 1.0 + (animationProgress - scaleStartProgress) * (scaleStartProgress / (1.0 - scaleStartProgress)) } } // 上方胶卷位置(与下方保持对称速度) private var topReelPosition: CGFloat { totalDistance * 0.9 * motionProgress } // 中间胶卷位置(主视觉移动) private var middleReelPosition: CGFloat { -totalDistance * 1.2 * motionProgress } // 下方胶卷位置(与上方保持对称速度) private var bottomReelPosition: CGFloat { totalDistance * 0.9 * motionProgress // 与上方速度完全一致 } } // MARK: - 胶卷组件 struct FilmReelView3: View { let images: [String] var body: some View { HStack(spacing: 10) { ForEach(images.indices, id: \.self) { index in FilmFrameView3(imageName: images[index]) } } } } struct FilmFrameView3: 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_Previews3: PreviewProvider { static var previews: some View { FilmAnimation1() } }