什么是复杂动画的良好抽象?

Dan*_*mov 27 language-agnostic user-interface animation abstraction design-patterns

您如何设计和实现复杂的UI交互动画?

(我不是在讨论像jQuery或UIKit这样的特定语言和库,除非它们强迫你以特定的方式思考管理相互依赖的动画,我感兴趣.)

考虑一个看似"简单"的任务,比如设计和编程iOS主屏幕.

iOS主屏幕

然而,隐藏复杂性的程度令人震惊.
我注意到关于界面的一些事情:

  • 当您几乎没有触摸图标时,其不透明度会发生变化,但尺寸更改会延迟.
  • 如果您在其他两个应用之间拖动应用,则在重新排列所有应用以移动可用空间之前会有明显的延迟.因此,如果您只是在屏幕上移动应用程序,那么在您解决之前不会发生任何事情.
  • 重新排列是逐行进行的,首先是你盘旋的线,它会触发链中的下一行,直到前面的空闲空间.
  • 如果你删除一个应用程序,它将会在现在可用的空间中掉落,而不仅仅是丢弃它的位置.
  • 如果您将应用程序悬停在另一个应用程序上,则会出现径向光,闪烁两次,然后才会创建一个组.
  • 如果该组是在自由空间中创建的,然后被丢弃,则会在放弃时动画留下以占据自由空间.

我确信这里有更多的复杂性,我没有注意到.

连续动画与离散动作

在粗略的概括,为每一对(animation, user_action)在同一界面环境下,你需要决定是否有什么user_action情况发生,而animation已经运行.

在大多数情况下,你可以

  • 取消动画;
  • 随时改变动画;
  • 忽略行动;
  • 将动作排队到动画结束时.

但是在动画期间可能会有几个动作,你必须决定在动画结束时丢弃哪些动作,排队哪些动作,以及是否执行所有排队动作,或者只执行最后一个动作.

如果在动画完成时某些内容排队,并且动画发生了变化,则需要确定排队的操作是否仍然有意义,或者是否需要删除.

如果这听起来太理论化,请考虑一个现实世界的例子:你如何处理用户向下拖动应用程序,等待重新排列开始,然后立即向上拖动应用程序并释放它?如何确保动画在每种可能的情况下都流畅可靠?

适合工作的正确工具

我发现自己甚至无法保留一半可能的情景.随着UI的表现力增加,可能状态的数量开始猛烈地违反7±2规则.

因此,我的问题如下:

你如何控制设计和实现动画的复杂性?

我既想找到解决问题的有效方法,也想找到解决问题的方法.

作为一个例子,事件和观察者被证明是大多数UI的非常有效的抽象.
但是,您能否依靠事件作为主要抽象来设计和实现类似iOS的拖放式屏幕?

代码必须多么纠结才能准确地表示UI的所有可能状态?它是一个事件处理程序添加另一个事件处理程序当一些布尔变量对于将其设置为false的函数为真时,除非在它之前运行另一个事件处理程序吗?

"你有没有听说过课程?"你可能想知道.为什么,我有,但这些类需要分享的状态太多了.

总而言之,我正在寻找与语言无关(尽管可能是语言或框架启发的)技术,用于管理顺序或同时发生的复杂的相互依赖,可取消,动画,并描述它们如何对用户操作做出反应.

(所有这一切都考虑到我不需要自己编写动画 - 也就是说,我确实可以访问像jQuery或Core Animation这样的框架,animate(styles, callback)对我而言,我可以cancel做到这一点.)

数据结构,设计模式,DSL如果有助于解决这些问题,那么它们都很好.

Dan*_*mov 34

在所描述的问题中,存在系统状态的隐含概念.动画是有状态的,因此它们的任何组合都是有状态的,可以说更是如此.

思考状态和动作的一种方法是有限状态机.

阅读 本文,解释如何应用FSM在JavaScript中实现自定义工具提示:

在此输入图像描述

这个例子可能看起来有些复杂,但确实说明了这一点:有限状态机迫使你思考什么状态是可能的,它们之间的转换是有效的,什么时候应该被触发以及什么代码应该被执行.

由于IBM的文章禁止使用其代码示例,因此我建议您阅读Ben Nadel使用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");
    }
};
Run Code Online (Sandbox Code Playgroud)

请注意,事件处理程序在进入状态时绑定,在离开此状态时未绑定.

FSM在解决这个问题时给出的最大优势是它们使状态明确.

虽然每个动画可能对包含系统的状态有所贡献,但您的系统永远不会同时处于两个状态或根本没有状态,并且调试几乎变得微不足道,因为您总是可以看到系统(或每个子系统)的状态,鉴于你的州设计是有道理的.

此外,通过强制您明确设计状态,使用FSM可以排除您不考虑特定状态/操作组合的可能性.没有"未定义的行为",因为每个转换都是明确的,并且是您的FSM设计的一部分.


如果你已经读过这篇文章,你可能会对Additive Animations(另一个介绍)感兴趣.它们现在已经在iOS 8中默认使用,并且已经被Kevin Doughty提倡了好几年了.

这是一种不同的方法,在保持系统状态的同时,允许多个(甚至相反的)动画同时处于活动状态.这可以给你疯狂的结果,但这是一个有趣的概念.

主要思想是避免将动画定义为从绝对值A到绝对值B的东西,而是将动画定义为相对于它们的最终值(每个动画从-Delta变为0).这允许您通过在每个时间点对它们的相对值求和来无缝地组合多个动画,并避免由反转或取消引起的尖峰:

添加动画http://ronnqvi.st/images/additive-interaction.gif

有关添加动画的准系统框架无关的示例,请查看alexkuz的添加动画模块(演示).


如果您已经读过这篇文章,那么您一定非常对动画感兴趣!目前,我对反应状态流方法很感兴趣.它建议将动画表达为懒惰的状态序列.这开辟了许多可能性,例如表达无限动画,逐渐添加和删除动画中的变换等等.

如果你想阅读一篇关于动画的文章,我建议这是成楼的动画思考.