Дизайн Ян Австрейх
4 726

Разработка анимации на iOS — восемь примеров интерфейса с кодом

Перевод материала дизайнера Натана Гиттера.

В закладки
Аудио

На WWDC 2018 дизайнеры Apple представили доклад под названием «Разработка текучих интерфейсов», в котором раскрыли детали жестового интерфейса IPhone X.

Презентация «Разработка текучих интерфейсов» на WWDC18

Это мой любимый доклад — очень рекомендую к просмотру.

В нём автор дал некоторые технические рекомендации, но то был псевдокод, в котором многое осталось непонятным.

Отрывок кода на Swift в презентации

Если вы попытаетесь реализовать эти идеи на практике, то обнаружите пропасть между идеей и тем, как её воплотить

Моя цель — преодолеть эту пропасть, представив рабочий код для всех примеров из презентации.

Мы создадим восемь интерфейсов: кнопки, пружинную анимацию, пользовательские взаимодействия и многое другое

Что будет в этой статье:

  • Краткое описание презентации «Проектирование текучих интерфейсов».
  • Восемь текучих интерфейсов, теория дизайна, на которой они строятся, и код для их создания.
  • Практические рекомендации для дизайнеров и разработчиков.

Что такое текучие интерфейсы

Текучий интерфейс также называют «быстрым» (fast), «гладким» (smooth) или «волшебным» (magical). Это опыт пользователя, когда он чувствует, что всё идёт как надо.

В презентации WWDC говорится о текучих интерфейсах как о «продолжающих ваш разум» и «продолжении естественного мира». Интерфейс можно назвать текучим, если он ведёт себя в соответствии с тем, как думают люди, а не машины.

Что делает их текучими

Они легко реагируют, в них можно прервать начатое действие и их можно перенацелить. Вот пример жеста «сдвинуть, чтобы вернуться на домашний экран» на IPhone X.

Приложение можно закрыть, пока идёт анимация запуска

Интерфейс сразу реагирует на жест пользователя, его можно остановить в любой момент процесса и даже изменить курс на полпути.

Зачем нужны текучие интерфейсы

  • Текучие интерфейсы улучшают опыт пользователя, делая каждое взаимодействие быстрым, лёгким и значимым.
  • Они дают пользователю чувство контроля, которое укрепляет доверие к вашему приложению и бренду.
  • Их сложно разработать. Текучий интерфейс трудно скопировать. Это может стать вашим конкурентным преимуществом.

Интерфейсы

Ниже я покажу, как построить восемь интерфейсов, которые охватывают все основные темы презентации.

Иконки, представляющие восемь интерфейсов, которые мы разработаем

Первый интерфейс: кнопка калькулятора

Это кнопка, которая подражает поведению кнопок в приложении калькулятора iOS.

Ключевые особенности:

  1. Выделяется цветом при нажатии.
  2. Её можно коснуться, когда приложение ещё открывается.
  3. Пользователь может нажать на кнопку и, не отрывая палец, увести иконку за границы кнопки, чтобы отменить нажатие.
  4. Он может увести кнопку за её границы, но, передумав, вернуть на место, подтвердив этим желание её нажать.

Теория дизайна

Нам нужны отзывчивые кнопки, которые показывают пользователю, что они функциональны. Кроме того, должна быть возможность отменить действие, если пользователь передумал уже после того, как нажал на кнопку. Это позволит пользователям быстрее принимать решения, поскольку они смогут параллельно действовать и думать.

Слайды из презентации WWDC, на которых наглядно показано, как жесты, выполняемые параллельно с процессом обдумывания, помогают пользователю действовать быстрее Надпись сверху: «Линейные интерфейсы, жесты выполняются после обдумывания». Снизу: «Жесты выполняются параллельно с мыслью». Блоки слева направо: «мысль», «решение», «жест», «результат»

Код

Первым шагом для создания этой кнопки будет использование подкласса UIControl, а не подкласса UIButton (UIButton работал бы хорошо, но, поскольку мы настраиваем взаимодействие, нам не нужны его функции).

Далее мы используем UIControlEvents, чтобы назначить функции различным сенсорным взаимодействиям:

Мы группируем события touchDown и touchDragEnter в одно «событие» под названием touchDown, и мы можем сгруппировать события touchUpInside, touchDragExit и touchCancel в одно событие touchUp.

(Все доступные UIControlEvents можно посмотреть здесь.)

Это даёт нам две функции для обращения с анимацией:

На touchDown мы отменяем существующую анимацию, если это необходимо, и сразу же устанавливаем цвет выделения (в этом примере установлен светло-серый).

На touchUp мы создаём новый аниматор и запускаем анимацию. Использование UIViewPropertyAnimator упрощает отмену анимации выделения.

(Обратите внимание: этот вариант не в точности повторяет поведение кнопок в приложении калькулятора iOS. В приложении можно коснуться одной кнопки, но активировать другую, если перетащить палец внутрь второй.)

Второй интерфейс: пружинная анимация

Этот интерфейс показывает, как создать пружинную анимацию, указав «damping» (упругость) и «response» (скорость).

Ключевые особенности

  • Интерфейс выглядит дружелюбно.
  • Нет определённого времени продолжительности анимации.
  • Действие легко прервать.

Теория дизайна

Пружинную анимацию отличают скорость и естественный внешний вид. Она быстро начинается и большую часть времени постепенно приближается к своему окончательному состоянию. Это идеально подходит для создания отзывчивых интерфейсов.

О чём нужно помнить при создании пружинных анимаций:

  1. Пружинные анимации не должны быть упругими. Используя значение 1 для damping, вы создадите анимацию, которая медленно и без проблем останавливается. Большинство анимаций должны использовать значение параметра damping 1.
  2. Не думайте о продолжительности анимации. Теоретически пружина никогда полностью не успокаивается, и установив продолжительность, вы можете сделать анимацию неестественной. Вместо этого поиграйте со значениями damping и response, пока не почувствуете, что анимация ведёт себя как надо.
  3. Прерывание имеет решающее значение. Поскольку пружинная анимация большую часть времени находится в состоянии, близком к «спокойному», пользователи могут подумать, что анимация завершилась, и попытаться снова взаимодействовать с ней.

Код

В UIKit мы можем создать пружинную анимацию с помощью UIViewPropertyAnimator и объекта UISpringTimingParameters. К сожалению, нет инициализатора, который даёт просто выставить значения для damping и response. Ближе всего к идеалу инициализатор UISpringTimingParameters, который позволяет установить mass (массу), damping, stiffness (жёсткость) и initial velocity (начальную скорость).

UISpringTimingParameters (mass: CGFloat, stiffness: CGFloat, damping: CGFloat, initialVelocity: CGVector).

Нам нужно создать удобный инициализатор, который даст ввести значения для damping и response, сопоставляя их с требуемой массой (mass), жёсткостью (stiffness) и упругостью (damping).

Используя знания физики, мы можем вычислить нужные формулы.

Синим обозначены параметры, коричневым — неизвестные, а для всего, что отмечено зелёным, мы принимаем значение 1

Теперь мы можем создать собственные UISpringTimingParameters с нужными нам параметрами.

Так мы будем прописывать параметры пружинных анимаций для всех интерфейсов.

Законы физики, по которым работают пружинные анимации

Хотите углубиться в тему? Тогда советую к прочтению великолепный материал Кристиана Шнорра “Demystifying UIKit Spring Animations”.

Эта статья открыла для меня много нового. Огромное спасибо Кристиану за то, что помог мне понять математические вычисления, которые стоят за пружинными анимациями, и за то, что научил решать дифференциальные уравнения второго порядка.

Третий интерфейс: кнопка фонарика

Ещё одна кнопка, но её поведение сильно отличается. Вот как ведёт себя кнопка фонарика на экране блокировки IPhone X:

Ключевые особенности

  1. Для активации требует преднамеренного жеста 3D touch.
  2. Визуально намекает на необходимый жест.
  3. Тактильная обратная связь подтверждает активацию кнопки.

Теория дизайна

Apple хотела создать доступную кнопку, которую нельзя нажать случайно. Требуемое усилие при нажатии для активации фонарика — отличный выбор, но ему не хватало понятности и хорошей обратной связи.

Чтобы решить эти проблемы, поведение кнопки изменили: теперь она пружинит и увеличивается в размере, когда пользователь начинает сильнее давить на экран, чем намекает на нужный жест.

Кроме того, появилось два типа вибрации тактильной обратной связи: один, когда пользователь применяет необходимое усилие, и второй, когда кнопка активируется и прикладывать силу больше не нужно. Они имитируют поведение физической кнопки.

Код

Чтобы измерить величину силы, прилагаемой к кнопке, мы можем использовать объект UITouch.

Мы прописали изменение размера кнопки в зависимости от приложенной силы, так что кнопка растёт с увеличением давления на экран.

Кнопка может быть нажата, но не активирована, так что нам нужно отслеживать её текущее состояние.

Сила, которую нужно приложить, чтобы подтвердить нажатие кнопки, немного ниже, чем сила активации. Это убережёт пользователя от случайной активации и деактивации кнопки.

Для тактильной обратной связи мы используем генераторы обратной связи из UIKit.

Для пружинной анимации мы можем использовать UIViewPropertyAnimator с инициализаторами UISpringTimingParameters, которые мы создали ранее.

Четвёртый интерфейс: резиновый скроллинг

Резиновый скроллинг происходит, когда пользователь пытается сдвинуть там, где это невозможно. Например, когда он прокрутил страницу до конца и снова делает сдвиг вверх.

Ключевые особенности

  1. Интерфейс остаётся отзывчивым, даже если действие невозможно выполнить.
  2. Несовпадение движения пальца и результата на экране говорит пользователю о достигнутой границе.
  3. Изображение сдвигается тем меньше, чем дальше от границы пользователь начал жест.

Теория дизайна

Резиновый скроллинг — отличный способ указать на невыполнимость действия, который при этом даёт пользователю чувство контроля. Он мягко указывает границы, возвращая их в правильное состояние после резинового сдвига.

Код

К счастью, реализовать его просто: offset = pow(offset, 0.7).

Устанавливая значение экспоненты от 0 до 1, мы делаем так, чтобы страница смещалась тем меньше, чем дальше от границы пользователь начал сдвиг. Чем выше значение экспоненты, тем меньше будет смещение страницы, чем ниже, тем больше она сместится.

Чтобы чётче обозначить контекст, этот код обычно реализуется в UIPanGestureRecognizer. Смещение можно рассчитать как разницу между начальной и текущей точками касания и обозначить его с помощью преобразования трансляции.

Примечание: это решение не в точности повторяет резиновый скроллинг от Apple. Мне нравится такой метод из-за его простоты, но есть и более сложные решения для разных вариантов поведения.

Пятый интерфейс: приостановка работы приложения, основанная на ускорении жеста

Чтобы открыть панель многозадачности на iPhone X, пользователь делает сдвиг снизу вверх, приостановив палец на середине.

Ключевые особенности

  1. Момент паузы рассчитывается на основе быстроты совершения жеста.
  2. Более быстрая остановка приводит к более быстрому отклику.
  3. Нет таймеров.

Теория дизайна

Текучие интерфейсы должны быть быстрыми. Задержка из-за таймера, даже короткая, может создать впечатление вяло работающего интерфейса.

Вариант с остановкой, основанной на скорости жеста, особенно крут, потому что его время реакции зависит от движения пользователя. Если он быстро останавливает сдвиг, интерфейс быстро реагирует, если делает это медленно — реагирует медленно.

Код

Нужно измерить ускорение, с которым пользователь сделал сдвиг, для этого мы можем отследить последние значения скорости совершаемого жеста.

Этот код обновляет массив скоростей (velocities), чтобы всегда иметь последние семь показателей, которые используются для вычисления ускорения.

Чтобы определить, достаточное ли ускорение сейчас придал пользователь, мы можем измерить разницу между первой скоростью в нашем массиве и текущей.

Мы также проверяем, есть ли у движения минимальные смещение и скорость. Если жест потерял более 90% скорости, считается, что он приостановлен.

Мой вариант реализации не идеален. На моём тестировании он работает очень хорошо, но можно найти и лучший способ измерения ускорения.

Шестой интерфейс: вознаграждение усилия

Выдвижная панель, которая открывается упруго или неупруго в зависимости от скорости жеста.

Ключевые особенности

  1. Нажатие на панель открывает её без эффекта упругости.
  2. Смахивание вверх открывает её с эффектом упругости.
  3. Интерактивно, прерываемо и обратимо.

Теория дизайна

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

При нажатии на панель она открывается без эффекта упругости, что кажется уместным, ведь пользователь не придал импульса в определённом направлении.

При разработке пользовательских взаимодействий важно помнить, что интерфейсы могут иметь разные анимации для разных взаимодействий.

Код

Чтобы упростить логику в выборе между нажатием и панорамированием, мы можем использовать специальный подкласс распознавателя жестов, который сразу же переходит в состояние began при нажатии.

Это также позволит пользователю нажать на панель во время её движения, чтобы остановить его (как при нажатии на экран, когда содержимое страницы прокручивается). Чтобы удостовериться, хотел ли пользователь своим нажатием остановить выплывание панели, мы можем проверить, были равна скорость нулю, когда жест заканчивается, и продолжить анимацию.

Чтобы отреагировать на жест с ненулевой скоростью, сначала нужно рассчитать его скорость относительно общего остаточного перемещения.

Мы можем использовать эту относительную скорость, чтобы продолжить анимацию с параметрами timing, которые включают в себя немного пружинности.

Здесь мы создаём новый UIViewPropertyAnimator, чтобы вычислить время, которое должна занять анимация, чтобы мы могли обеспечить правильный durationFactor при продолжении анимации.

Есть и другие сложности, связанные с отменой анимации, на которых я не стал останавливаться в статье. Если вы хотите узнать больше по этой теме, прочитайте моё руководство “Building Better iOS App Animations”.

Седьмой интерфейс: FaceTime PiP

Повторное создание пользовательского интерфейса «картинка в картинке» (picture in picture — PiP) приложения iOS FaceTime.

Ключевые особенности

  1. Лёгкое, почти воздушное взаимодействие.
  2. Прогнозируемая позиция основана на скорости замедления UIScrollView.
  3. Непрерывная анимация, которая учитывает начальную скорость жестов.

Код

Наша конечная цель — написать что-то вроде этого.

Нужно создать анимацию с начальной скоростью, которая соответствует скорости жеста панорамирования и анимирует движение картинки до ближайшего угла.

Первым делом вычислим начальную скорость.

Для этого нам нужно рассчитать относительную скорость, основанную на текущей скорости, текущей позиции и расположении цели.

Мы можем разбить скорость на её X- и Y-компоненты и определить относительную скорость для каждого.

Затем вычислим угол, в который должна двигаться анимация PiP.

Чтобы интерфейс выглядел легко и естественно, нам нужно определить окончательную позицию PiP на основе его текущего движения. Что делать, если пользователь сдвинул картинку, остановил в случайной точке и отпустил её? Куда ей направиться?

Мы можем использовать скорость замедления UIScrollView для расчёта конечной позиции. Это удобно, потому что такое решение связано с мышечной памятью пользователя, в которой остался процесс прокрутки. Если пользователь знает, как далеко прокручивается страница при определённой скорости жеста прокрутки, он может использовать это знание, чтобы интуитивно угадать, сколько силы необходимо приложить для перемещения PiP в нужное место.

Эта скорость замедления помогает сделать взаимодействие лёгким — нужен лишь быстрый сдвиг, чтобы отправить PiP в полёт через весь экран.

Мы можем использовать функцию предугадывания, представленную в презентации «Разработка текучих интерфейсов», чтобы рассчитать конечную позицию.

Последняя недостающая часть — прописать, как картинке найти ближайший угол. Для этого мы можем пробежаться по всем положениям углов и найти то, которое находится на минимальном расстоянии от прогнозируемой позиции.

Подводим итог: мы используем скорость замедления UIScrollView для прогнозирования движения PiP и вычисляем относительную скорость, чтобы передать всё это в UISpringTimingParameters.

Восьмой интерфейс: поворот

Применение идей из создания интерфейса PiP к анимации вращения.

Ключевые особенности

  1. Использует функцию прогноза, чтобы соответствовать скорости жеста пользователя.
  2. Всегда поворачивает экран в нужное положение.

Код

Код здесь очень похож на предыдущий интерфейс PiP. Мы будем использовать одни и те же блоки, за исключением замены функции nearestCorner на closestAngle.

Теперь пришло время создать UISpringTimingParameters, для этого мы должны использовать CGVector для начальной скорости. Так как измерение одно, установите желаемую скорость в качестве значения dx и значение dy равным нулю.

Внутри аниматор будет игнорировать значение dy и использовать значение dx для создания кривой времени.

Попробуйте создать их сами

Эти интерфейсы смотрятся намного интереснее на реальном устройстве. Чтобы поиграть с ними, используйте демоприложение, которое доступно на GitHub.

Скриншоты из приложения

Практические рекомендации

Дизайнерам

  1. Воспринимайте интерфейсы как текучую среду, а не набор статичных элементов.
  2. Подумайте об анимации и жестах в начале процесса разработки. Инструменты вроде Sketch отлично работают, но не дают раскрыть все возможности интерфейса.
  3. Сделайте прототип вместе с разработчиками. Попросите разработчиков, которые смыслят в дизайне, помочь вам прототипировать анимацию, жесты и тактильную обратную связь.

Для разработчиков

  1. Применяйте советы из этой статьи к своим компонентам кода. Подумайте, как ещё их можно комбинировать.
  2. Просвещайте своих дизайнеров. Многие не знают всех возможностей 3D touch, тактильной обратной связи, жестов и пружинных анимаций.
  3. Создавайте прототипы вместе с дизайнерами. Помогите им увидеть, как созданный дизайн будет смотреться на устройстве, и создавайте инструменты, которые помогут им работать над дизайном более эффективно.
{ "author_name": "Ян Австрейх", "author_type": "self", "tags": [], "comments": 5, "likes": 41, "favorites": 110, "is_advertisement": false, "subsite_label": "design", "id": 50251, "is_wide": false, "is_ugc": true, "date": "Wed, 14 Nov 2018 15:26:16 +0300" }
{ "id": 50251, "author_id": 192524, "diff_limit": 1000, "urls": {"diff":"\/comments\/50251\/get","add":"\/comments\/50251\/add","edit":"\/comments\/edit","remove":"\/admin\/comments\/remove","pin":"\/admin\/comments\/pin","get4edit":"\/comments\/get4edit","complain":"\/comments\/complain","load_more":"\/comments\/loading\/50251"}, "attach_limit": 2, "max_comment_text_length": 5000, "subsite_id": 199114, "possessions": [] }

5 комментариев 5 комм.

Популярные

По порядку

1

Плюс, если тоже прочитали "Перевод материала дизайнера Натана Гитлера."

Ответить
1

спасибули!

Ответить

0

Ох уж этот фонарик)))

Ответить
0

Я ещё таких долгих фанфиков не читал...

Ответить
0

"Теоретически пружина никогда полностью не успокаивается"
Вы чё?

Ответить
0
{ "page_type": "article" }

Прямой эфир

[ { "id": 1, "label": "100%×150_Branding_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox_method": "createAdaptive", "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfl" } } }, { "id": 2, "label": "1200х400", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfn" } } }, { "id": 3, "label": "240х200 _ТГБ_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fizc" } } }, { "id": 4, "label": "240х200_mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "flbq" } } }, { "id": 5, "label": "300x500_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfk" } } }, { "id": 6, "label": "1180х250_Interpool_баннер над комментариями_Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "bugf", "p2": "ffyh" } } }, { "id": 7, "label": "Article Footer 100%_desktop_mobile", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjxb" } } }, { "id": 8, "label": "Fullscreen Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjoh" } } }, { "id": 9, "label": "Fullscreen Mobile", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjog" } } }, { "id": 10, "disable": true, "label": "Native Partner Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyb" } } }, { "id": 11, "disable": true, "label": "Native Partner Mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyc" } } }, { "id": 12, "label": "Кнопка в шапке", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "p1": "bscsh", "p2": "fdhx" } } }, { "id": 13, "label": "DM InPage Video PartnerCode", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox_method": "createAdaptive", "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "bugf", "p2": "flvn" } } }, { "id": 14, "label": "Yandex context video banner", "provider": "yandex", "yandex": { "block_id": "VI-223676-0", "render_to": "inpage_VI-223676-0-1104503429", "adfox_url": "//ads.adfox.ru/228129/getCode?pp=h&ps=bugf&p2=fpjw&puid1=&puid2=&puid3=&puid4=&puid8=&puid9=&puid10=&puid21=&puid22=&puid31=&puid32=&puid33=&fmt=1&dl={REFERER}&pr=" } }, { "id": 15, "label": "Плашка на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byudx", "p2": "ftjf" } } }, { "id": 16, "label": "Кнопка в шапке мобайл", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byzqf", "p2": "ftwx" } } }, { "id": 17, "label": "Stratum Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fzvb" } } }, { "id": 18, "label": "Stratum Mobile", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fzvc" } } }, { "id": 19, "label": "Тизер на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "p1": "cbltd", "p2": "gazs" } } } ]
Компания отказалась от email
в пользу общения при помощи мемов
Подписаться на push-уведомления
{ "page_type": "default" }