Jakie są dobre abstrakcje dla złożonych animacji?

Jak podejść do projektowania i wdrażania złożonych animacji interakcji UI?

(nie mówię o konkretnych językach i bibliotekach, takich jak jQuery czy UIKit, chyba że zmuszą cię do specyficznego myślenia o zarządzaniu współzależnymi animacjami, które mnie interesują.)

Rozważ zwodniczo "proste" zadanie, takie jak projektowanie i programowanie ekranu głównego iOS.

ekran główny iOS

Jednakże wielkość ukrytego złożoność jest zdumiewająca .
Tylko kilka rzeczy, które zauważyłem w interfejsie:

  • gdy tylko dotkniesz ikony, jej krycie zmienia się, ale zmiana rozmiaru jest opóźniona.
  • przeciągnięcie aplikacji między dwoma innymi aplikacjami powoduje zauważalne opóźnienie przed zmianą kolejności aplikacji w celu przesunięcia wolnego miejsca. Jeśli więc po prostu przesuwasz aplikację po ekranie, nic się nie dzieje, dopóki się nie osiedlisz.
  • przestawianie odbywa się linijka po linijce, najpierw idzie linia, nad którą wisiałeś, a uruchamia następną linię w łańcuchu, do linii, w której wcześniej znajdowała się wolna przestrzeń.
  • jeśli upuścisz aplikację, spadnie ona na wolne miejsce, a nie tylko tam, gdzie ją upuściłeś.
  • jeśli najedziesz kursorem na inną aplikację, pojawi się światło promieniowe, mrugnie dwukrotnie i dopiero wtedy zostanie utworzona grupa.
  • jeśli grupa została utworzona bezpośrednio do wolnej przestrzeni, a następnie odrzucona, zostanie ożywiona w lewo, aby zająć wolną przestrzeń podczas odrzucania.

I ' m sure there czy jest tu jeszcze bardziej skomplikowana, że nie zauważyłem.

Ciągłe animacje vs dyskretne działania

W ogólnym uogólnieniu, dla każdej pary (animation, user_action) w tym samym kontekście interfejsu należy zdecydować co jeśli user_action dzieje się, gdy animation jest już działa .

W większości przypadków można

  • Anuluj animację;
  • Zmień animację w podróży;
  • Ignoruj działanie;
  • Kolejka akcji do kiedy animacja kończy się.

Ale wtedy może być kilka akcji podczas animacji i musisz zdecydować, które akcje odrzucić, które ustawić w kolejce i czy wykonać wszystkie akcje w kolejce, czy tylko ostatnią, gdy animacja się skończy.

Jeśli coś zostanie ustawione w kolejce po zakończeniu animacji, a animacja zostanie zmieniona, musisz zdecydować, czy akcje w kolejce mają sens, czy trzeba je usunąć.

Jeśli brzmi to zbyt teoretycznie, rozważ realny świat przykład: Jak poradzić sobie z użytkownikiem przeciągającym aplikację w dół, czekającym na rozpoczęcie zmiany układu, a następnie natychmiast przeciągającym aplikację w górę i zwalniającym ją? Jak zapewnić płynną i wiarygodną animację w każdym możliwym przypadku?

Odpowiednie narzędzia do pracy

Nie jestem w stanie utrzymać nawet połowy możliwych scenariuszy w głowie. Wraz ze wzrostem ekspresji interfejsu użytkownika, liczba możliwych stanów zaczyna gwałtownie naruszać 7±2 reguła .

Moje pytanie jest zatem następujące:

Jak oswoić złożoność projektowania i wdrażania animacji?

Interesuje mnie zarówno znalezienie skutecznych sposobów myślenia o problemie, jak i sposobów jego rozwiązania.

Jako przykład, zdarzenia i obserwatorzy okazały się bardzo skuteczną abstrakcją dla większości Ui {18]}.
Ale czy można zaprojektować i zaimplementować ekran drag-n-drop podobny do iOS, opierając się na zdarzeniach jako główna abstrakcja?

Jak splątany musi być kod, aby dokładnie reprezentować wszystkie możliwe Stany interfejsu użytkownika? Czy jest to funkcja obsługi zdarzeń dodająca inną funkcję obsługi zdarzeń, gdy jakaś zmienna logiczna jest prawdziwa do funkcji, która ustawia ją na false, chyba że przed nią uruchomiła się jeszcze inna funkcja obsługi zdarzeń?

"Nigdy nie słyszałeś o zajęciach?"możesz się zastanawiać. Mam, ale jest zbyt wiele stanów, którymi te klasy będą chciały się podzielić.

Podsumowując, Szukam język-agnostyczny (chociaż prawdopodobnie inspirowane językiem lub frameworkiem) techniki zarządzania złożonymi, współzależnymi, cancelable, animacjami dzieje się w sekwencji lub naraz, i opisujące, jak reagują na działania użytkownika .

(wszystko to biorąc pod uwagę, że nie muszę sam programować animacji-tzn. mam dostęp do frameworka jQuery lub Core Animation, który może animate(styles, callback) to coś dla mnie, a ja mogę cancel to.)

Struktury danych, wzorce projektowe, DSL są dobre, jeśli pomagają rozwiązać problem.

Author: Dan Abramov, 2012-06-01

1 answers

W opisywanym problemie istnieje pojęcie stanu systemu . Animacje są stanowe, a zatem każda ich kompozycja jest tak samo stanowa, a prawdopodobnie nawet bardziej.

Jednym ze sposobów myślenia o stanach i działaniach byłyby Maszyny skończonych Stanów .

Przeczytaj Ten artykuł wyjaśniający, jak zastosować FSM do zaimplementowania niestandardowej podpowiedzi w JavaScript:

Tutaj wpisz opis obrazka

Ten przykład może wydawać się nieco zawiły, ale robi zilustruj punkt: maszyna skończonych Stanów zmusza cię do myślenia jakie stany są możliwe, jakie przejścia między nimi są ważne, kiedy powinny być wyzwalane i jaki kod powinien być wykonany, gdy są .

Ponieważ artykuł IBM zabrania używania jego próbki kodu , zachęcam do przeczytania artykułu Bena Nadela, który używa FSM do implementacji rozwijanego widżetu menu.

Ben pisze:

I ' ve seen the way that Finite State Maszyny mogą podjąć duże, złożone zadania i rozbić je na mniejsze, znacznie łatwiejsze do opanowania Stany. Próbowałem nawet zastosować ten rodzaj mentalności do bindowania i rozłączania procedur obsługi zdarzeń w JavaScript . Ale, teraz, że jestem coraz bardziej komfortowe z maszyn stanowych i, w szczególności, przejścia stanu, Chciałem Spróbować i zastosować ten sposób myślenia do spójnego interfejsu użytkownika (UI) widget.

Oto nieco rozebrana Wersja jego kodu:

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

Uwaga to procedury obsługi zdarzeń są związane podczas wprowadzania stanu i niezwiązane podczas opuszczania tego stanu.

Największą zaletą FSMs w rozwiązaniu tego problemu jest to, że sprawiają, że stan jest jawny.

Podczas gdy każda animacja może przyczynić się do stanu obejmującego system, Twój system nigdy nie może być w dwóch stanach naraz lub w ogóle, a debugowanie staje się niemal trywialne, ponieważ zawsze możesz zobaczyć, w jakim stanie system (lub każdy podsystem) jest, biorąc pod uwagę twój stan design ma sens.

Ponadto, zmuszając cię do jawnego projektowania Stanów, użycie FSMs wyklucza możliwość, że nie myślisz o konkretnej kombinacji stanu/działań. Nie ma "niezdefiniowanego zachowania", ponieważ każde przejście jest jawne i jest częścią twojego projektu FSM.


Jeśli przeczytałeś tak daleko, być może zainteresują cię dodatkowe animacje (another intro ). Są teraz domyślne w systemie iOS 8 i zostały poparte przez Kevina Doughty już od kilku lat.

Jest to inne podejście, w którym zachowując stan systemu, zezwalasz na aktywację kilku (nawet przeciwnych) animacji w tym samym czasie. To może dać ci szalone Wyniki , ale to ciekawa koncepcja.

Główną ideą jest unikanie definiowania animacji jako czegoś przechodzącego od wartości bezwzględnej A do wartości bezwzględnej B, a zamiast tego definiowanie animacji jako względem ich wartości końcowej(każda animacja przechodzi od-Delta do 0). To pozwala na płynne łączenie kilku animacji poprzez sumowanie ich wartości względnych w każdym punkcie czasu i unikanie skoków spowodowanych odwróceniem lub anulowaniem:

animacje dodatkowe
(źródło: ronnqvi.st)

Dla barebone Framework-agnostyczny przykład animacji addytywnych, sprawdź moduł alexkuz addytywnej animacji ( demo).


Jeśli czytałeś tak daleko, musisz być naprawdę zainteresowany animacjami! Obecnie jestem zaintrygowany podejściem react-state-stream . Proponuje wyrażanie animacji jako leniwych sekwencji Stanów. Otwiera to wiele możliwości, takich jak wyrażanie nieskończonych animacji, stopniowe dodawanie i usuwanie przekształceń z animacji itp.

Jeśli chcesz przeczytać jeden artykuł o animacji, proponuję, aby myśli o animacji autor: Cheng Lou.

 34
Author: Dan Abramov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-20 17:31:55