Last active
October 7, 2024 19:02
-
-
Save eldaroid/93efbe3de6a258de0a4dde362fd4411d to your computer and use it in GitHub Desktop.
RU: Визуализация различных типов анимационных кривых и наблюдение их поведения: в реальном времени. EN: Visualize different types of animation curves and observe their behavior: in real time
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct AnimationExample: View { | |
@ObservedObject var trace = AnimationTrace() | |
@State var animating: Bool = false | |
@State var selectedAnimationIndex: Int = 0 | |
@State var slowAnimations: Bool = false | |
var selectedAnimation: (String, Animation) { | |
return animations[selectedAnimationIndex] | |
} | |
var body: some View { | |
VStack { | |
RoundedRectangle(cornerRadius: 10) | |
.fill(Color.pink) | |
.frame(width: 50, height: 50) | |
.offset(x: animating ? 100 : -100) | |
.modifier(RecordTimingCurve(onChange: { | |
self.trace.record($0) | |
}, animatableData: animating ? 1 : 0)) | |
VStack { | |
TracePath(values: trace.data.map { | |
(CGFloat($0), $1) | |
}) | |
.stroke(Color.red, style: .init(lineWidth: 2)) | |
.frame(height: 150) | |
.background(Rectangle().stroke(Color.gray, style: .init(lineWidth: 1))) | |
HStack { | |
Text("0") | |
Spacer() | |
Text("\(trace.endTime - trace.startTime)") | |
} | |
}.frame(width: 200) | |
Spacer() | |
Picker(selection: $selectedAnimationIndex, label: EmptyView(), content: { | |
ForEach(0..<animations.count) { | |
Text(animations[$0].0) | |
} | |
}) | |
Button(action: { | |
self.animating = false | |
self.trace.reset() | |
withAnimation(self.selectedAnimation.1.speed(self.slowAnimations ? 0.25 : 1), { | |
self.animating = true | |
}) | |
}, label: { Text("Animate") }) | |
Toggle(isOn: $slowAnimations, label: { Text("Slow Animations") }) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let animations: [(String, Animation)] = [ | |
// MARK: - Timing curve | |
("default", .default), | |
("linear(duration: 1)", .linear(duration: 1)), | |
(".easeIn(duration: 1)", .easeIn(duration: 1)), | |
(".easeOut(duration: 1)", .easeOut(duration: 1)), | |
(".easeInOut(duration: 1)", .easeInOut(duration: 1)), | |
//MARK: - Spring | |
(".spring", .spring()), | |
("smooth", Animation.smooth), // no bounce | |
("snappy", Animation.snappy), // small bounce | |
("bouncy", Animation.bouncy), // medium bounce | |
("interpolatingSpring(stiffnes: 5, damping: 3)", .interpolatingSpring(stiffness: 5, damping: 3)), | |
("interactiveSpring(response: 3, dampingFraction: 2, blendDuration: 1)", .interactiveSpring(response: 3, dampingFraction: 2, blendDuration: 1)), | |
// MARK: - Higher order | |
(".default.repeatCount(3)", Animation.default.repeatCount(3)), | |
("delay", Animation.default.delay(2)), | |
("speed", Animation.default.repeatForever()), | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Класс, который отслеживает и сохраняет | |
// данные анимации для последующего отображения. | |
final class AnimationTrace: ObservableObject { | |
let objectWillChange = PassthroughSubject<(), Never>() | |
var data: [(time: CFTimeInterval, value: CGFloat)] = [] | |
var startTime: CFTimeInterval { | |
data.first?.time ?? 0 | |
} | |
var endTime: CFTimeInterval { | |
data.last?.time ?? 0 | |
} | |
func record(_ value: CGFloat) { | |
data.append((CACurrentMediaTime(), value)) | |
DispatchQueue.main.async { | |
print("Data count: \(self.data.count)") | |
self.objectWillChange.send() | |
} | |
} | |
func reset() { | |
data = [] | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// RecordTimingCurve используется для отслеживания текущего | |
// значения анимации и вызова функции onChange | |
// при каждом изменении этого значения. | |
struct RecordTimingCurve: GeometryEffect { | |
var onChange: (CGFloat) -> () = { _ in () } | |
var animatableData: CGFloat = 0 { | |
didSet { | |
onChange(animatableData) | |
} | |
} | |
func effectValue(size: CGSize) -> ProjectionTransform { | |
return .init() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Структура, реализующая протокол Shape, | |
// которая строит путь на основе значений анимации. | |
struct TracePath: Shape { | |
var values: [(CGFloat, CGFloat)] // the second component should be in range 0...1 | |
func path(in rect: CGRect) -> Path { | |
guard let f = values.first, let l = values.last else { return Path() } | |
let xOffset = f.0 | |
let xMultiplier = l.0 - f.0 | |
return Path { p in | |
p.move(to: CGPoint(x: rect.minX, y: rect.maxY)) | |
for value in values { | |
let point = CGPoint(x: rect.minX + ((value.0 - xOffset) / xMultiplier) * rect.width, y: rect.maxY - CGFloat(value.1) * rect.height) | |
p.addLine(to: point) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Согласно эплу, существую виды анимации:
Timing curve (по дефолту duration=0.35сек.):
Spring
Higher order: