Лекция 8. Анимация (часть 1). CS193P Spring 2023.

Ниже представлен фрагмент Лекции 8 Стэнфордского курса CS193P Весна 2023 «Разработка iOS приложений с помощью SwiftUI«.
Полный русскоязычный неавторизованный конспект Лекции 8 в формате Google Doc и в виде PDF-файла, который можно скачать и использовать offline, доступны на платной основе.
Код находится на GitHub.

С полным перечнем Лекций и Домашних Заданий на русском языке можно познакомиться здесь.

. . . . . . . . . . . . . .

Итак, я говорил вам в начале семестра, что я постараюсь научить вас всему на этом курсе как минимум три раза. Один раз я расскажу вам об этом на слайдах, потом я покажу это вам на демонстрационном примере, а потом вы сами сделаете это в своем Домашнем Задании.
И это в большой степени верно для анимации.
Итак, вот та часть, где я рассказываю вам про анимацию, то есть какие бывают анимации, как это работает и так далее.
Начнем с фундаментальных основ анимации: что нужно знать, чтобы действительно понять, что такое анимация.
Главное, что нужно понять, это то, что анимация просто показывает изменения в вашей модели  Model с течением времени. Вот и все.

Анимация показывает вам ИЗМЕНЕНИЯ с течением времени, и эти изменения отражаются через аргументы модификаторов ViewModifier и, очевидно, что геометрические фигуры Shape могут меняться.

И еще, я выделил это в отдельную вещь “Переходы” (transitions), но вы увидите, что на самом деле это всего лишь первый вариант, то есть когда Views приходят на экран и уходят с экрана, то работает пара модификаторов ViewModifier.
Когда View появляется на экране, вы хотите, чтобы он “проявлялся” постепенно.
Когда View уходит за пределы экрана, вы хотите, чтобы он «улетал» или постепенно «растворялся» или что-то вроде этого.
Итак, это три вида изменений, которые происходят, и которые мы пытаемся анимировать.
И теперь, пожалуй, самая важная строка на этом слайде.

Анимация показывает вам изменения, которые уже произошли, и анимация просто показывает их вам растянутыми во времени.

Людям, которые не занимались асинхронным программированием или чем-то подобным, нужно привыкнуть к этому. Вам хочется думать, если у вас есть анимация, длящаяся в течение 5 секунд, например, анимация непрозрачности opacity или что-то в этом роде, что переменная var, которая устанавливает непрозрачность opacity как-то меняется со временем, но это не так.
Когда вы устанавливаете непрозрачность opacity для View на 1 от 0, то View постепенно «проявляется» (fade in) в течение 5 секунд, но непрозрачность opacity для вашего View мгновенно изменилась на 1 для всего вашего View.
Просто система анимация показала пользователю это изменение в течение 5 секунд.

Итак, все изменения, которые стоят за анимациями, которые вы видите, произошли мгновенно, a вы их видите растянутыми во времени.
Как только вы поймете это и отложите в своей голове, то сможете вложить гораздо больше смысла в написание кода анимации, который будет правильно работать.

Таким образом, модификаторы ViewModifiers — это основные агенты изменений в пользовательском интерфейсе (UI). Большинство вещей, которые меняются, например непрозрачность opacity, соотношение сторон aspectRatio, даже Views, летающие вокруг, это просто модификатор ViewModifier position.
Есть такой модификатор ViewModifier, который я вам еще не показывал, он называется position, и такие контейнеры Views, как HStack и LazyVGrid, используют этот модификатор position для размещения своих Views. Когда Views “летают” вокруг, это просто потому, что аргумент в их модификаторе позиция position меняется.
Итак, модификаторы ViewModifiers — это действительно то, что заставляет всё двигаться и анимировать, и вы увидите это в демонстрационном примере, который мы сделаем.

Одна вещь, которую вы должны понять относительно изменения в аргументах ViewModifiers, это то, что они будут анимировать ТОЛЬКО ПОСЛЕ того, как View было выведено на экран.
Итак, View появляется на экране с определенной непрозрачностью opacity и определенным соотношением сторон aspectRatio, но View не находится на экране, не происходит  анимации из некоторых случайных значений opacity и aspectRatio. Когда View появится на экране, оно будет иметь начальные значения opacity и aspectRatio. А вот потом, если они будут изменятся, тогда вы увидите анимацию.
Люди иногда запутываются в этом. Они установят свою непрозрачность opacity в какое-то значение и ждут, что View будет каким-то образом анимироваться к этому значению непрозрачности opacity, но анимации не будет на экране. Непрозрачность opacity этого View не имела прежнего значения. Так что всё начинается со значения, которое opacity имеет, когда появляется на экране.

Не все модификаторы ViewModifier имеют анимируемые аргументы, но я бы сказал, что огромное большинство — это 100% модификаторов, о которых вы могли бы подумать, можно анимировать.

Основные способы прихода и ухода View с экрана

Когда View появляется на экране или уходит с экран, и когда происходит эта анимация, то анимируется View целиком. Весь View целиком “растворяется” или целиком “улетает” или весь целиком увеличивается или уменьшается в размерах.
При этом View, появляющийся на экране, анимирует только в том случае, если Views-контейнер, в котором он должен появиться на экране, уже находится на экране.
И то же самое, если ваш View выходит за пределы экрана, он анимируется только в том случае, если он уходит из какого-то Views-контейнера, который остается на экране.
Если View появляется на экране вместе со своим контейнером, тогда он не будет анимирован.
Вы получаете анимацию при выходе на экран только, если вы уже в Views-контейнере и этот контейнер уже находится на экране.
Потому что, если Views-контейнер вместе с вашим View появляется на экране, тогда единственная анимация, которая произойдет, это анимация самого Views-контейнере целиком, a не View внутри этого контейнера.

А как Views появляются на экране и уходят с экрана?
В основном благодаря if-else внутри @ViewBuilders. Если if “что-то” true, то покажи этот aView, иначе else покажи мне другой bView. Если условие “что-то” изменится, то на экран придет aView, a другой bView уйдет с экрана.
Кроме того, есть ForEach — хитрый способ, благодаря которому Views приходят и уходят с экрана
Потому что ForEach — это как взять массив вещей и создать View для каждой из этих вещей. Ну, а если взять что-то убрать из массива, то это уничтожит View, который пришел с убранным элементом массива. Это также способ для View “уйти” с экрана и “вернуться” обратно на экран, если вы добавите удаленную из массива вещь обратно в массив.
Итак, основным способом появления и исчезновения Views на экране являются операторы if-else или switch внутри @ViewBuilder и ForEach.

Как запустить анимацию?

Как сделать так, чтобы эти анимации происходили, как их запускать?

Есть три способа запуска анимации.

Один способ анимации — неявная (implicit) анимация. Мы видели эту анимацию, когда делали перетасовку карт Shuffle. Идея здесь в том, что мы используем этот ViewModifier с именем .animation(Animation, value:) и заставляем анимировать в View, к которому мы его применяем, все, что связано с изменением значения value. Мы можем разместить несколько таких неявных анимаций с различными value. Различные value будут вызывать разные виды анимации.
Итак, это неявная (implicit) анимация. Это все, что вы когда-либо видели в этом курсе.

Второй способ анимации — явная (explicit) анимация. Мы оборачиваем какой-то код с помощью withAnimation { } прямо в коде. И это заставляет все изменения, которые происходят внутри withAnimation { }, анимировать вместе.
Это на самом деле основной способ создания анимации, a не неявный (implicit) способ анимация.
Мы делаем это с помощью этого явного (explicit) способа анимации.
И первое, что я собираюсь сделать, в демонстрационном примере, это .осуществить переключение с неявной (implicit) анимации, которую мы делали, на явную (explicit) анимацию, которая на самом деле и должна быть.

И, наконец, третий способ анимации — путем включения и исключения Views в (из) UI, это также будет анимироваться независимо. Потому что я говорил вам, что мы можем запустить анимацию, если мы удалим View из UI. Этот View будет как бы “растворяться” и уйдет из UI. Так что это то, что мы можем сделать, удалять или добавлять View на UI, и это вызовет анимацию.

И это действительно единственные три вещи, которые вызывают анимацию. Все это будет происходить только в том случае, если анимации происходят в Views, которые уже являются частью вашего UI и находятся на экране.
Мы можем анимировать изменения ТОЛЬКО ПОСЛЕ того, как мы выходим на экран.
Я уже сказал вам дважды об этом, и это уже третий раз, когда я опять это повторяю.
Я просто хочу, чтобы вы это взяли cебе в голову, потому что в вашем Домашнем Задании вы, наверняка, попробуете сделать что-то, почти гарантирую это, вы скажете: “Это не анимирует!!” И это потому, что View появилось на экране, но вы не изменили его после того, как оно появилось на экране.

. . . . . . . . . . . . .

Это небольшой фрагмент Лекции 8.
Далее на Лекции 8 рассматриваются следующие вопросы:

  • Наблюдатели Свойств (Property Observers)
  • onChange(of:)
  • Анимация
  • Основные способы прихода и ухода View с экрана
  • Как запустить анимацию?
  • Implicit (неявная) анимация
  • Explicit (явная) анимация
  • Transitions (Переходы). AnyTransition.
  • Matched Geometry Effect
  • Модификатор .onAppear
  • Как происходит анимация?
  • Переменная var animatableData
  • Демонстрационные примеры
  • Избавления от неявной анимации Shuffle
  • Явная анимация choose(card)
  • Неявная (implicit) анимация для празднования “совпадения” карт
  • Анимируемый модификатор Cardify. Протокол Animatable
  • Счет score и .animation(nil)
  • “Летающие” числа (FlyingNumbers) (начало)

Полный русскоязычный неавторизованный конспект Лекции 8 в формате Google Doc и в виде PDF-файла, который можно скачать и использовать offline, доступны на платной основе.
Код находится на GitHub.

С полным перечнем Лекций и Домашних Заданий Стэнфордского курса CS193P Весна 2023 «Разработка iOS приложений с помощью SwiftUI» на русском языке можно познакомиться здесь.