Это седьмая Лекция курса Stanford CS193p, весна 2021 года. Лекция 7 и Лекция 8 полностью посвящены анимации. Мы собираемся выяснить, как она работает. По ходу дела нам придется изучить модификаторы ViewModifier, которые очень сильно вовлечены в процесс анимации.
Объясняется протокол ViewModifier, который затем используется для того, чтобы сделать возможным преобразование любого View в карту игры Memorize путем «картафикации» ( от слова «карта»). Затем лекция переходит к углубленному рассмотрению анимации и начинает комплексную многолекционную демонстрацию анимации.
Если Лекция 8 — это, по существу, демо-Лекция, на которой профессор Пол Хэгерти демонстрирует все возможные анимации, какие только вы можете себе представить, то на Лекции 7 изложены теоретические основы анимации и представлено начало (неявная анимация, чтобы заставить смайлик на Memorize карте вращаться, когда она совпадает с другой картой, и модификаторы ViewModifier) той огромной демонстрации, которая последует на Лекции 8.
Прежде чем погрузиться в механизм анимации и действительно понять, как заставить анимацию появиться на экране, профессор излагает основные принципы, золотые правила анимации.
Золотые правила анимации.
Только изменения могут анимироваться. Если ничего не изменяется, то ничего не будет анимироваться. Это фундаментальное правило.
Какого рода изменения могут анимироваться?
Реально в SwiftUI, только 3 типа изменений:
- аргументы модификаторов ViewModifiers,
- геометрические фигуры Shapes, то есть аргументы при создании Shapes,
- “появление” или «уход» Views на UI.
Необходимо понимать, что анимация показывает пользователю изменения, которые уже произошли. Ваш код не работает в темпе с анимацией. Ваш код только вносит изменения в то, о чем мы упоминали выше. A затем система анимации показывает пользователю растянутые во времени изменения, которые уже были сделаны в вашем коде. Ваш код уже ушел с этого места. Это требует небольшой коррекции мышления, но если вы это поймете, то сможете вложить гораздо больше смысла в написание кода анимации, который будет правильно работать.
Модификаторы ViewModifiers — это основные “агенты изменений” в UI, но изменение аргументов ViewModifier должно произойти ПОСЛЕ того, как View разместился на UI. Другими словами, изменения аргументов модификаторов рассматриваются системой анимации только ПОСЛЕ момента присоединения View к UI.
Ещё одна вещь, которую необходимо понимать, это то, что не все модификаторы ViewModifiers имеют анимируемые аргументы. Например, у модификатора .font аргументы не являются анимируемыми. Но большинство модификаторов ViewModifier, как и ожидалось, являются анимируемыми. Даже цвета Color и подобные им вещи могут анимироваться.
Что касается появления на экране и ухода с экрана Views, то здесь действуют те же ограничения, анимация будет работать, только если этот View появляется или уходит из контейнера Views, который уже находится на UI. Так что, если View появляется на экране вместе с контейнером, то тогда контейнер будет анимироваться, a не View внутри этого контейнера. С помощью .onAppear можно узнать, когда контейнер появился на экране, и уже там заставить View выйти на экран, если вы хотите анимировать View отдельно.
Три способа запуска анимаций.
Один способ — неявная анимация, то есть автоматически, с помощью специального ViewModifier с именем .animation, который принимает в качестве аргумента Animation. Неявные или “автоматические” анимации с модификатор ..animation обычно не являются основным источником анимационного “поведения” в приожении. Они преимущественно используются для Views — “листьев” в иерархии Views (то есть автономных) или “кирпичиков” Лего из-за странности ее применения для контейнеров, когда она распространяется на все Views внутри контейнера.
Второй способ — явная анимация, это тот случай, когда вы собираетесь сделать что-то, что вызовет многочисленные изменения и вам нужен весь UI, чтобы координировать анимацию всех этих изменений. Явная анимация выполняется с помощью очень простой Swift функции с именем withAnimation. Она также берет в качестве аргумента Animation.
И, наконец, третий способ — путем включения и исключения Views в / из UI, это также будет анимироваться, если снабдить эти Views модификатором .transition и выполнить явную анимацию с withAnimation. Появление Views на экране и уход Views с экрана в основном происходят благодаря if-else во ViewBuilders, это самый распространенный случай, но есть еще и ForEach. У ForEach есть массив Array и элементы этого массива меняются, это приводит к тому, что Views, соответствующие этим элементам, могут исключаться или включаться в UI. “Под капотом” переход .transition — это ничего более, чем пара модификаторов ViewModifiers. Один из этих модификаторов — это “ДО” модификации View, которое находится в движении на / с UI. Другой модификатор — это “ПОСЛЕ” модификация View, которое находится в движении на / с UI. Таким образом, .transition — это на самом деле просто версия анимации “изменений аргументов ViewModifiers”.
Все эти анимации будет происходить только в Views, которые уже являются частью вашего UI. Профессор постоянно повторяет эту фразу на протяжении Лекции 7, потому что часто студенты пишут свой код анимации и декларируют: «О, моя анимация просто не работает. Я пишу код правильно.» Неработоспособность кода анимации связана с тем, что те вещи, которые меняются и, как студенты ожидают, должны анимироваться, происходят с Views, которые либо еще не на экране, либо их изменения происходят одновременно с появлением Views на экране и не могут быть затем анимированы. Views уже должны быть на экране перед началом изменений.
Фактическая анимация создается модификаторами ViewModifiers и геометрическими фигурами Shapes. Каково их участие в целой анимационной системе?
По сути, система анимации делит продолжительность анимации
duration на крохотные кусочки в зависимости от “кривой анимации” curve. А затем просит Shapes и ViewModifiers, которые являются Animatable, нарисовать этот кусочек, нарисовать этот кусочек, нарисовать этот кусочек, и они рисуются снова и снова и снова и затем все это собирается вместе и превращается в маленький фильм, что, собственно, и представляет собой анимацию. Это работает невероятно элегантно и просто.
Взаимодействие между системой анимации и ViewModifiers и Shapes осуществляется с помощью единственной переменной var animatableData. Переменная var animatableData находится в протоколе Animatable, и это единственная там переменная var. Всё, что вам необходимо сделать — реализовать
то есть создать переменную var animatableData. Если вы являетесь геометрической фигурой Shape или модификатором ViewModifier, то вы можете участвовать в этой анимации маленькими кусочками.
Это великолепная Лекция на тему анимации, нигде вы не найдете столько материала и столько нюансов использования различных типов анимации.
Все концепции анимации были теоретически разобраны профессором на слайдах и воплощены в демонстрационном приложении Memorize для анимации переворота карт, создания новой игры и начисления бонусов при быстром поиске «совпадающих» карт. Анимация — мощная подсистема SwiftUI, так что нам не понадобилось добавлять слишком много строк кода в наше приложение, чтобы оно делало такие сумасшедшие вещи.
Демонстрационная часть этой Лекции 7 касалась только неявной анимации. Неявная анимация оказалась очень тонкой вещью, которая очень чувствительна к местоположению модификатора .animation, а в случае, если анимация направлена модификаторы ViewModifiers имеют анимируемые аргументы. Например, у модификатора .font аргументы не являются анимируемыми.
Код демонстрационного примера для Лекции 7 находится на Github для iOS 14 в папке Memorize L7.
Русскоязычный неавторизованный конспект Лекции 7, иллюстрированный, хронометрированный и представленный в виде PDF-файла, который можно скачать и использовать offline, а также в формате Google Doc доступны на платной основе.