design patterns - ¿Cuáles son buenas abstracciones para animaciones complejas?
design-patterns animation (1)
¿Cómo enfocas el diseño y la implementación de animaciones de interacción UI complejas?
(No estoy hablando de lenguajes específicos y bibliotecas como jQuery o UIKit, a menos que lo obliguen a una forma específica de pensar sobre la administración de animaciones interdependientes, lo que me interesa).
Considere una tarea engañosamente "simple" como diseñar y programar la pantalla de inicio de iOS.
Sin embargo, la magnitud de la complejidad oculta es asombrosa .
Solo unas pocas cosas que noté sobre la interfaz:
- Cuando apenas tocas un ícono, su opacidad cambia pero el cambio de tamaño se retrasa.
- Si arrastra una aplicación entre otras dos aplicaciones, hay un retraso notable antes de que todas las aplicaciones se reorganicen para mover el espacio libre. Entonces, si sigues moviendo una aplicación por la pantalla, no pasa nada hasta que te instales.
- La reorganización ocurre línea por línea, primero va por la línea sobre la que se movió, y desencadena la siguiente línea en la cadena, hasta la línea donde anteriormente estaba el espacio libre.
- Si suelta una aplicación, caerá en el espacio ahora libre, y no solo en el lugar donde lo dejó caer.
- Si coloca una aplicación sobre otra, aparecerá una luz radial, parpadeará dos veces y solo entonces se creará un grupo.
- Si el grupo se creó directamente en el espacio libre y luego se descarta, se animará hacia la izquierda para ocupar el espacio libre mientras descarta.
Estoy seguro de que hay aún más complejidad aquí que no pude notar .
Animaciones continuas frente a acciones discretas
En general, para cada par de (animation, user_action)
en el mismo contexto de interfaz, debe decidir qué sucede si user_action
sucede mientras la animation
ya se está ejecutando .
En la mayoría de los casos, puedes
- Cancele la animación;
- Cambiar la animación sobre la marcha;
- Ignora la acción;
- Ponga en cola la acción cuando finalice la animación.
Pero luego puede haber varias acciones durante la animación, y usted tiene que decidir qué acciones desechar, qué cola, y si ejecutar todas las acciones en cola, o solo la última, cuando termine la animación.
Si algo se pone en cola cuando finaliza la animación y se cambia la animación, debe decidir si las acciones en cola aún tienen sentido o si deben eliminarse.
Si esto te parece demasiado teórico, considera un ejemplo del mundo real : ¿cómo lidias con el usuario arrastrando una aplicación hacia abajo, esperando que comience la reorganización, y luego inmediatamente arrastrando la aplicación hacia arriba y soltándola? ¿Cómo se asegura que la animación sea fluida y creíble en todos los casos posibles?
Herramientas correctas para el trabajo
Me encuentro incapaz de mantener incluso la mitad de los posibles escenarios en la cabeza. A medida que aumenta la expresividad de la IU, la cantidad de estados posibles comienza a violar violentamente la regla de 7 ± 2 .
Mi pregunta, por lo tanto, es la siguiente:
¿Cómo dominas la complejidad en el diseño e implementación de animaciones?
Estoy interesado tanto en encontrar maneras efectivas de pensar sobre el problema, como en los medios para resolverlo.
Como ejemplo, los eventos y los observadores demostraron ser una abstracción muy efectiva para la mayoría de las UI .
¿Pero puede diseñar e implementar una pantalla de arrastrar y soltar similar a la de iOS, basándose en eventos como la abstracción principal?
¿Qué tan enredado debe ser el código para representar con precisión todos los estados posibles de la IU? ¿Sería un controlador de eventos agregar otro controlador de eventos cuando alguna variable booleana es verdadera para la función que la establece en falsa, a menos que otro controlador de eventos se haya ejecutado antes?
"¿Nunca has oído hablar de clases?", Te preguntarás. Por qué, lo hice, pero hay demasiado estado que estas clases querrán compartir.
En resumen, busco técnicas que sean independientes del lenguaje (aunque probablemente estén inspiradas en el lenguaje o el marco) para gestionar animaciones interdependientes y cancelables complejas que se realicen de forma secuencial o simultánea, y que describan cómo reaccionan ante las acciones del usuario .
(Todo esto teniendo en cuenta que no tengo que programar animaciones en sí, es decir, tengo acceso a un marco como jQuery o Core Animation que puede animate(styles, callback)
para mí y puedo cancel
).
Las estructuras de datos, los patrones de diseño, las DSL son buenas si ayudan al problema.
En el problema descrito, hay una noción implícita de estado del sistema . Las animaciones son explícitas, y por lo tanto, cualquier composición de ellas es tan explícita, y podría decirse que incluso más.
Una forma de pensar acerca de los estados y las acciones serían las máquinas de estado finito .
Lea este artículo que explica cómo aplicar FSM para implementar una información sobre herramientas personalizada en JavaScript:
Este ejemplo puede parecer un poco intrincado pero ilustra el punto: la máquina de estado finito te obliga a pensar qué estados son posibles, qué transiciones entre ellos son válidas, cuándo deben activarse y qué código se debe ejecutar una vez que lo sean .
Debido a que el artículo de IBM prohíbe el uso de su muestra de código , lo animo a leer también un artículo de Ben Nadel que usa FSM para implementar un widget de menú desplegable.
Ben escribe:
He visto la forma en que las máquinas de estados finitos pueden llevar a cabo tareas grandes y complejas y dividirlas en estados más pequeños y mucho más manejables. Incluso he tratado de aplicar este tipo de mentalidad a los controladores de eventos vinculantes y no vinculantes en JavaScript . Pero, ahora que me estoy sintiendo más cómodo con las máquinas de estado y, en particular, las transiciones de estado, quería probar y aplicar esta forma de pensar a un widget de interfaz de usuario coherente (UI).
Aquí hay una versión ligeramente reducida de su código:
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");
}
};
Tenga en cuenta que los controladores de eventos están vinculados al ingresar un estado y se liberan al salir de este estado.
La mayor ventaja que brindan los FSM al resolver este problema es que hacen que el estado sea explícito .
Si bien cada animación puede contribuir al estado del sistema abarcador, su sistema nunca puede estar en dos estados a la vez o no tener estados, y la depuración se vuelve casi trivial porque siempre se puede ver en qué estado se encuentra el sistema (o cada subsistema). dado el diseño de su estado, tiene sentido.
Además, al obligarlo a diseñar estados explícitamente, el uso de FSM descarta la posibilidad de que no piense en una combinación particular de estado / acciones. No existe un "comportamiento indefinido" porque cada transición es explícita y forma parte de su diseño de FSM.
Si ha leído hasta aquí, le pueden interesar las animaciones aditivas ( otra introducción ). Ahora están predeterminados en iOS 8 y Kevin Doughty los defiende desde hace varios años.
Este es un enfoque diferente, en el que, manteniendo el sistema en estado, permite que varias animaciones (incluso las opuestas) estén activas al mismo tiempo. Esto puede darte resultados locos, pero es un concepto interesante.
La idea principal es evitar definir la animación como algo que va del valor absoluto A al valor absoluto B, y en su lugar definir las animaciones como relativas a su valor final (cada animación va de -Delta a 0). Esto le permite combinar varias animaciones sin problemas sumando sus valores relativos en cada punto de tiempo, y evitar los picos causados por cancelaciones o cancelaciones:
animaciones aditivas http://ronnqvi.st/images/additive-interaction.gif
Para obtener un ejemplo agnóstico de estructuras de animación aditiva, consulte el módulo de animación aditiva ( demo ) de alexkuz .
Si has leído hasta aquí, ¡debes estar realmente interesado en las animaciones! Actualmente, estoy intrigado por el enfoque de react-state-stream . Propone expresar animaciones como secuencias perezosas de estados. Esto abre muchas posibilidades, como expresar animaciones infinitas, agregar y eliminar gradualmente las transformaciones de las animaciones, etcétera.
Si quieres leer un artículo sobre animación, sugiero que sea Pensamientos sobre animación de Cheng Lou.