Какие абстракции подходят для сложных анимаций?

Как вы подходите к разработке и реализации сложных анимаций взаимодействия с пользовательским интерфейсом?

(Я не говорю о конкретных языках и библиотеках, таких как jQuery или UIKit, если только они не заставляют вас думать о управлении взаимозависимыми анимациями, что меня интересует.)

Рассмотрим обманчиво «простую» задачу, такую ​​как проектирование и программирование домашнего экрана iOS.

Главный экран iOS

Однако масштабы скрытой сложности поразительны.
Вот несколько вещей, которые я заметил в интерфейсе:

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

Я уверен, что здесь есть еще большая сложность, которую я не заметил.

Непрерывная анимация против дискретных действий

Грубо говоря, для каждой пары (animation, user_action) в одном контексте интерфейса вам нужно решить, что, если user_action произойдет, пока animation уже запущен.

В большинстве случаев вы можете

  • Отмените анимацию;
  • Меняйте анимацию на ходу;
  • Игнорируйте действие;
  • Поставьте действие в очередь до завершения анимации.

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

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

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

Правильные инструменты для работы

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

Поэтому мой вопрос таков:

Как справиться со сложностями в разработке и реализации анимации?

Я заинтересован как в поиске эффективных способов осмысления проблемы, так и в способах ее решения.

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

Насколько запутанным должен быть код, чтобы точно отображать все возможные состояния пользовательского интерфейса? Будет ли это обработчик событий, добавляющий еще один обработчик событий, когда некоторая логическая переменная истинна для функции, которая устанавливает для нее значение false, если до нее не запускался еще один обработчик событий?

«Вы никогда не слышали о классах?» вы можете задаться вопросом. Да, да, но есть слишком много состояний, которыми эти классы захотят поделиться.

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

(Все это с учетом того, что мне не нужно программировать анимацию самостоятельно, т. Е. У меня есть доступ к фреймворку, подобному jQuery или Core Animation, который может animate(styles, callback) для меня, и я могу cancel это. )

Структуры данных, шаблоны проектирования, DSL - все это хорошо, если они помогают решить проблему.


person Dan Abramov    schedule 01.06.2012    source источник


Ответы (1)


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

Один из способов думать о состояниях и действиях - это машины с конечным числом состояний.

Прочтите эту статью, в которой объясняется, как применить FSM для реализации настраиваемой всплывающей подсказки в JavaScript. :

введите описание изображения здесь

Этот пример может показаться немного запутанным, но действительно иллюстрирует суть: конечный автомат заставляет вас думать, какие состояния возможны, какие переходы между ними допустимы, когда они должны запускаться и какой код должен выполняться, когда они < / сильный>.

Поскольку статья IBM запрещает использование ее образца кода, Я рекомендую вам также прочитать статья Бена Надела, в которой FSM используется для реализации виджета раскрывающегося меню.

Бен пишет:

Я видел, как конечные автоматы могут брать большие сложные задачи и разбивать их на более мелкие, гораздо более управляемые состояния. Я даже пытался применить этот образ мышления к привязка и отключение обработчиков событий в JavaScript. Но теперь, когда я начинаю более комфортно работать с конечными автоматами и, в частности, с переходами между состояниями, я хотел попробовать применить это мышление к виджету связного пользовательского интерфейса (UI).

Вот немного урезанная версия его кода:

var inDefault = {
    description: "I am the state in which only the menu header appears.",
    setup: function() {
       dom.menu.mouseenter(inHover.gotoState);
    },    
    teardown: function() {
         dom.menu.unbind("mouseenter");
    }
};

var inHover = {
    description: "I am the state in which the user has moused-over the header of the menu, but the menu has now shown yet.",
    setup: function() {
        dom.menu.addClass("menuInHover");
        dom.menu.mouseleave(inDefault.gotoState);
        dom.header.click(
            function(event) {
                event.preventDefault();
                gotoState(inActive); 
            }
       );    
    },
    teardown: function() {
        dom.menu.removeClass("menuInHover");
        dom.menu.unbind("mouseleave");
        dom.header.unbind("click"); 
    }    
};

var inActive = {
     description: "I am the state in which the user has clicked on the menu and the menu items have been shown. At this point, menu items can be clicked for fun and profit.",

    setup: function() {
        dom.menu.addClass("menuInActive");
        dom.stage.mousedown(
            function(event) {
                var target = $(event.target);
                if (!target.closest("div.menu").length) {
                    gotoState(inDefault); 
                } 
            }
       );
       dom.header.click(
            function(event) {
                event.preventDefault();
                 gotoState(inHover);

            }
       );
       dom.items.delegate(
            "li.item",
            "click",
            function(event) {
                console.log(
                    "Clicked:",
                    $.trim($(this).text())
               );

            }
       );
    },    
    teardown: function() {
        dom.menu.removeClass("menuInActive"); 
        dom.stage.unbind("mousedown", inDefault.gotoState);
        dom.header.unbind("click"); 
        dom.items.undelegate("li.item", "click");
    }
};

Обратите внимание, что обработчики событий привязываются при входе в состояние и отключаются при выходе из этого состояния.

Самым большим преимуществом конечных автоматов при решении этой проблемы является то, что они делают состояние явным.

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

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


Если вы дочитали до этого места, возможно, вас заинтересует Аддитивные анимации (другое введение) . Теперь они используются по умолчанию в iOS 8 и уже несколько лет поддерживаются Кевином Даути.

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

Основная идея состоит в том, чтобы не определять анимацию как нечто, идущее от абсолютного значения A к абсолютному значению B, и вместо этого определять анимацию относительно их окончательного значения (каждая анимация идет от -Delta до 0). Это позволяет вам легко комбинировать несколько анимаций, суммируя их относительные значения в каждый момент времени, и избегать всплесков, вызванных разворотами или отменами:

аддитивные анимации
(источник: ronnqvi.st)

Чтобы увидеть пример аддитивной анимации, не зависящий от платформы barebone-фреймворка, ознакомьтесь с модулем аддитивной анимации alexkuz (демо).


Если вы дочитали до этого места, значит, вас действительно интересует анимация! В настоящее время меня заинтриговал подход response-state-stream. Он предлагает выражать анимацию как ленивую последовательность состояний. Это открывает множество возможностей, таких как выражение бесконечных анимаций, постепенное добавление и удаление преобразований из анимаций и т. Д.

Если вы хотите прочитать одну статью об анимации, я предлагаю это Мысли об анимации пользователя Cheng Lou.

person Dan Abramov    schedule 01.06.2012
comment
PS Если вам интересно сделать состояние явным с помощью JS, ознакомьтесь с React. Я не могу рекомендовать его достаточно для создания сложных пользовательских интерфейсов на JS. - person Dan Abramov; 23.05.2014