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

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

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

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

домашний экран iOS

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

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

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

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

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

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

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

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

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

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

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

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

Поэтому мой вопрос заключается в следующем:

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

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

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

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

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

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

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

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

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");
    }
};

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

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

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

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


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

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

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

http://ronnqvi.st/images/additive-interaction.gif

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


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

Если вы хотите прочитать одну статью об анимации, я предлагаю Ченг Лу: " Мысли об анимации ".

Другие вопросы по тегам