Цепная анимация в SwiftUI
Я работаю над относительно сложной анимацией в SwiftUI и задаюсь вопросом, какой самый лучший / самый элегантный способ связать различные фазы анимации.
Допустим, у меня есть представление, которое сначала нужно масштабировать, затем подождать несколько секунд, а затем исчезнуть (а затем подождать пару секунд и начать заново - до бесконечности).
Если я попытаюсь использовать несколько блоков withAnimation() в одном представлении / стеке, они в конечном итоге будут мешать друг другу и портить анимацию.
Лучшее, что я мог придумать, это вызвать пользовательскую функцию в модификаторе.onAppear() начальных представлений и в этой функции иметь блоки withAnimation() для каждого этапа анимации с задержками между ними. Итак, в основном это выглядит примерно так:
func doAnimations() {
withAnimation(...)
DispatchQueue.main.asyncAfter(...)
withAnimation(...)
DispatchQueue.main.asyncAfter(...)
withAnimation(...)
...
}
В итоге получается довольно долго и не очень "красиво". Я уверен, что должен быть лучший / более хороший способ сделать это, но все, что я пробовал до сих пор, не дало мне точный поток, который я хочу.
Любые идеи / рекомендации / советы будут высоко оценены. Спасибо!
1 ответ
Как упоминалось в других ответах, в настоящее время в SwiftUI нет механизма для цепочки анимаций, но вам необязательно использовать ручной таймер. Вместо этого вы можете использоватьdelay
функция для связанной анимации:
withAnimation(Animation.easeIn(duration: 1.23)) {
self.doSomethingFirst()
}
withAnimation(Animation.easeOut(duration: 4.56).delay(1.23)) {
self.thenDoSomethingElse()
}
withAnimation(Animation.default.delay(1.23 + 4.56)) {
self.andThenDoAThirdThing()
}
Я обнаружил, что это приводит к более гладкой цепной анимации, чем при использовании DispatchQueue
или Timer
, возможно, потому, что он использует один и тот же планировщик для всех анимаций.
Жонглирование всеми задержками и продолжительностью может быть проблемой, поэтому амбициозный разработчик может абстрагировать вычисления в какой-то глобальный withChainedAnimation
функция, чем делает это за вас.
Использование таймера работает. Это из моего собственного проекта:
@State private var isShowing = true
@State private var timer: Timer?
...
func askQuestion() {
withAnimation(Animation.easeInOut(duration: 1).delay(0.5)) {
isShowing.toggle()
}
timer = Timer.scheduledTimer(withTimeInterval: 1.6, repeats: false) { _ in
withAnimation(.easeInOut(duration: 1)) {
self.isShowing.toggle()
}
self.timer?.invalidate()
}
// code here executes before the timer is triggered.
}
Боюсь, на данный момент нет поддержки для чего-то вроде ключевых кадров. По крайней мере, они могли бы добавить onAnimationEnd()
... но такого нет.
Там, где мне удалось немного повезти, - анимация траекторий. Хотя ключевых кадров нет, у вас больше контроля, так как вы можете определить свои "AnimatableData". Например, проверьте мой ответ на другой вопрос: /questions/49548685/kak-ozhivit-put-v-swiftui/49548694#49548694
В этом случае, это в основном дуга, которая вращается, но увеличивается от нуля до некоторой длины, и в конце поворота она постепенно возвращается к нулевой длине. Анимация имеет 3 фазы: сначала один конец дуги движется, а другой - нет. Затем они оба движутся вместе с одинаковой скоростью, и, наконец, второй конец достигает первого. Мой первый подход состоял в том, чтобы использовать идею DispatchQueue, и она работала, но я согласен: это ужасно безобразно. Затем я понимаю, как правильно использовать AnimatableData. Так что... если вы оживляете пути, вам повезло. В противном случае, похоже, нам придется ждать возможности более элегантного кода.