wake-ios/wake/View/Blind/Box.swift
2025-08-22 18:58:08 +08:00

301 lines
10 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
struct FilmStripView: View {
@State private var animate = false
// 使SF Symbols
private let symbolNames = [
"photo.fill", "heart.fill", "star.fill", "bookmark.fill",
"flag.fill", "bell.fill", "tag.fill", "paperplane.fill"
]
private let targetIndices = [2, 5, 3] //
var body: some View {
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
//
FilmStrip(
symbols: symbolNames,
targetIndex: targetIndices[0],
offset: 0,
stripColor: .red
)
.rotationEffect(.degrees(5))
.zIndex(1)
FilmStrip(
symbols: symbolNames,
targetIndex: targetIndices[1],
offset: 0.3,
stripColor: .blue
)
.rotationEffect(.degrees(-3))
.zIndex(2)
FilmStrip(
symbols: symbolNames,
targetIndex: targetIndices[2],
offset: 0.6,
stripColor: .green
)
.rotationEffect(.degrees(2))
.zIndex(3)
}
.onAppear {
withAnimation(
.timingCurve(0.2, 0.1, 0.8, 0.9, duration: 4.0)
) {
animate = true
}
}
}
}
//
struct FilmStrip: View {
let symbols: [String]
let targetIndex: Int
let offset: Double
let stripColor: Color
@State private var animate = false
var body: some View {
GeometryReader { geometry in
let itemWidth: CGFloat = 100
let spacing: CGFloat = 8
let totalWidth = itemWidth * CGFloat(symbols.count) + spacing * CGFloat(symbols.count - 1)
//
RoundedRectangle(cornerRadius: 10)
.fill(stripColor.opacity(0.8))
.frame(height: 160)
.overlay(
// 齿
HStack(spacing: spacing) {
ForEach(0..<symbols.count * 3, id: \.self) { index in
Circle()
.fill(Color.black)
.frame(width: 12, height: 12)
.offset(y: -75)
}
}
.frame(width: totalWidth * 3),
alignment: .leading
)
.overlay(
//
HStack(spacing: spacing) {
ForEach(0..<symbols.count * 3, id: \.self) { index in
let actualIndex = index % symbols.count
ZStack {
RoundedRectangle(cornerRadius: 6)
.fill(Color.white)
.frame(width: itemWidth - 10, height: 100)
Image(systemName: symbols[actualIndex])
.font(.system(size: 30))
.foregroundColor(stripColor)
.shadow(radius: 2)
}
}
}
.offset(x: animate ? -CGFloat(targetIndex) * (itemWidth + spacing) - totalWidth : 0)
.frame(width: totalWidth * 3),
alignment: .leading
)
}
.frame(height: 180)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + offset) {
withAnimation(
.timingCurve(0.2, 0.1, 0.8, 0.9, duration: 3.5)
) {
animate = true
}
}
}
}
}
//
struct EnhancedFilmStrip: View {
let symbols: [String]
let targetIndex: Int
let offset: Double
let stripColor: Color
@State private var animate = false
var body: some View {
GeometryReader { geometry in
let itemWidth: CGFloat = 110
let spacing: CGFloat = 10
let totalWidth = itemWidth * CGFloat(symbols.count) + spacing * CGFloat(symbols.count - 1)
ZStack {
//
RoundedRectangle(cornerRadius: 12)
.fill(Color.black.opacity(0.3))
.frame(height: 170)
.offset(y: 5)
.blur(radius: 3)
//
RoundedRectangle(cornerRadius: 12)
.fill(stripColor)
.frame(height: 170)
.overlay(
// 齿
HStack(spacing: spacing) {
ForEach(0..<symbols.count * 3, id: \.self) { index in
VStack {
Circle()
.fill(Color.black)
.frame(width: 14, height: 14)
.padding(.top, 8)
Spacer()
Circle()
.fill(Color.black)
.frame(width: 14, height: 14)
.padding(.bottom, 8)
}
.frame(height: 170)
}
}
.frame(width: totalWidth * 3),
alignment: .leading
)
//
HStack(spacing: spacing) {
ForEach(0..<symbols.count * 3, id: \.self) { index in
let actualIndex = index % symbols.count
ZStack {
RoundedRectangle(cornerRadius: 8)
.fill(Color.white)
.frame(width: itemWidth - 15, height: 110)
.shadow(color: .black.opacity(0.2), radius: 3, x: 0, y: 2)
VStack {
Image(systemName: symbols[actualIndex])
.font(.system(size: 32, weight: .bold))
.foregroundColor(stripColor)
Text("\(actualIndex + 1)")
.font(.caption)
.foregroundColor(.gray)
}
}
}
}
.offset(x: animate ? -CGFloat(targetIndex) * (itemWidth + spacing) - totalWidth : 0)
.frame(width: totalWidth * 3)
}
}
.frame(height: 180)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + offset) {
withAnimation(
.timingCurve(0.2, 0.1, 0.8, 0.9, duration: 3.5)
) {
animate = true
}
}
}
}
}
//
struct FilmStripView_Previews: PreviewProvider {
static var previews: some View {
FilmStripView()
}
}
// 使
struct EnhancedFilmStripView: View {
@State private var animate = false
private let symbolNames = [
"camera.fill", "film.fill", "photo.fill", "heart.fill",
"star.fill", "bookmark.fill", "flag.fill", "bell.fill"
]
private let targetIndices = [2, 5, 3]
var body: some View {
ZStack {
LinearGradient(
gradient: Gradient(colors: [Color.blue.opacity(0.8), Color.purple.opacity(0.8)]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.edgesIgnoringSafeArea(.all)
VStack(spacing: 30) {
Text("胶片动效展示")
.font(.title)
.fontWeight(.bold)
.foregroundColor(.white)
.padding(.top)
EnhancedFilmStrip(
symbols: symbolNames,
targetIndex: targetIndices[0],
offset: 0,
stripColor: .red
)
.rotationEffect(.degrees(4))
EnhancedFilmStrip(
symbols: symbolNames,
targetIndex: targetIndices[1],
offset: 0.4,
stripColor: .blue
)
.rotationEffect(.degrees(-2))
EnhancedFilmStrip(
symbols: symbolNames,
targetIndex: targetIndices[2],
offset: 0.8,
stripColor: .green
)
.rotationEffect(.degrees(3))
Button("重新播放") {
restartAnimation()
}
.padding()
.background(Color.white)
.foregroundColor(.blue)
.cornerRadius(10)
.padding()
}
}
.onAppear {
startAnimation()
}
}
private func startAnimation() {
withAnimation(
.timingCurve(0.2, 0.1, 0.8, 0.9, duration: 4.0)
) {
animate = true
}
}
private func restartAnimation() {
animate = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
startAnimation()
}
}
}
//
struct EnhancedFilmStripView_Previews: PreviewProvider {
static var previews: some View {
EnhancedFilmStripView()
}
}