27

複雑なUIインタラクションアニメーションの設計と実装にどのようにアプローチしますか?

(私が興味を持っている相互依存アニメーションの管理について特定の考え方を強いられない限り、jQueryやUIKitのような特定の言語やライブラリについては話していません。)

iOSホーム画面の設計やプログラミングのような一見「単純な」タスクを考えてみましょう。

iOSホーム画面

ただし、隠れた複雑さの大きさは驚くべきものです。
インターフェイスについて気付いたことがいくつかあります。

  • アイコンにかろうじて触れると、不透明度は変わりますが、サイズの変更は遅れます。
  • 他の2つのアプリ間でアプリをドラッグすると、すべてのアプリが再配置されて空き領域が移動するまでに、顕著な遅延が発生します。そのため、画面上でアプリを動かし続けると、落ち着くまで何も起こりません。
  • 再配置は行ごとに行われ、最初にホバーした行に移動し、チェーン内の次の行をトリガーして、以前は空き領域があった行に移動します。
  • アプリをドロップすると、ドロップした場所だけでなく、現在は空きスペースにドロップされます。
  • アプリを別のアプリの上に置くと、放射状のライトが表示され、2回点滅してから、グループが作成されます。
  • グループが空き領域の右側に作成されてから破棄された場合、グループは左側をアニメーション化して、破棄中に空き領域を占有します。

ここでは、気付かなかった複雑さがさらに増していると確信しています。

連続アニメーションと離散アクション

大まかに一般化すると(animation, user_action)、同じインターフェイスコンテキスト内のの各ペアについて、すでに実行されているときにuser_actionが発生animationするかを決定する必要があります。

ほとんどの場合、

  • アニメーションをキャンセルします。
  • 外出先でアニメーションを変更します。
  • アクションを無視します。
  • アニメーションが終了するまでアクションをキューに入れます。

ただし、アニメーション中にいくつかのアクションが発生する可能性があり、アニメーションが終了したときに、破棄するアクション、キューに入れるアクション、およびキューに入れられたすべてのアクションを実行するか、最後のアクションのみを実行するかを決定する必要があります。

アニメーションの終了時に何かがキューに入れられ、アニメーションが変更された場合、キューに入れられたアクションがまだ意味があるかどうか、または削除する必要があるかどうかを判断する必要があります。

これが理論的すぎると思われる場合は、実際の例を考えてみてください。ユーザーがアプリを下にドラッグし、再配置が開始されるのを待ってから、すぐにアプリを上にドラッグして放すとどうなりますか?考えられるすべてのケースで、アニメーションがスムーズで信頼できるものであることをどのように確認しますか?

仕事に適したツール

考えられるシナリオの半分でも頭の中で維持することができないことに気づきました。UIの表現力が高まるにつれて、可能な状態の数は7±2のルールに激しく違反し始めます

したがって、私の質問は次のとおりです。

アニメーションの設計と実装の複雑さをどのように抑えますか?

私は問題についての効果的な考え方とそれを解決する手段の両方を見つけることに興味があります。

例として、イベントとオブザーバーは、ほとんどのUIにとって非常に効果的な抽象化であることが証明されました
しかし、主な抽象化としてイベントに依存するiOSのようなドラッグアンドドロップ画面を設計および実装できますか?

UIのすべての可能な状態を正確に表すには、コードがどの程度絡み合っている必要がありますか?さらに別のイベントハンドラーがその前に実行されていない限り、ブール変数がfalseに設定される関数にtrueの場合、別のイベントハンドラーを追加するのはイベントハンドラーでしょうか?

「クラスのことを聞いたことがありませんか?」あなたは不思議に思うかもしれません。なぜ、私は持っていますが、これらのクラスが共有したいと思う状態が多すぎます。

要約すると、私は、シーケンスまたは一度に発生する複雑な相互依存のキャンセル可能なアニメーションを管理し、それらがユーザーのアクションにどのように反応するかを説明するための、言語に依存しない(おそらく言語またはフレームワークに触発された)手法を探しています。

(アニメーション自体をプログラムする必要がないことを考えると、これはすべてです。つまり、jQueryやCore Animationanimate(styles, callback)のような、自分に合ったフレームワークにアクセスできcancelます。)

データ構造、デザインパターン、DSLは、問題の解決に役立つのであれば、すべて優れています。

4

1 に答える 1

34

説明されている問題には、システム状態の暗黙の概念があります。アニメーションはステートフルであるため、アニメーションの構成はすべてステートフルであり、間違いなくさらにステートフルです。

状態とアクションについて考える1つの方法は、有限状態マシンです。

FSMを適用してJavaScriptでカスタムツールチップを実装する方法を説明するこの記事をお読みください 。

ここに画像の説明を入力してください

この例は少し複雑に見えるかもしれませんが、要点を示しています。有限状態マシンは、どの状態が可能で、どの状態が有効で、いつトリガーされるべきか、そしてどのコードが一度実行されるべきかを考えるように強制します。

IBMの記事ではコードサンプルの使用が禁止されているため、FSMを使用してドロップダウンメニューウィジェットを実装するBenNadelの記事も読むことをお勧めします。

ベンは書いています:

有限状態機械が大きくて複雑なタスクを実行し、それをより小さく、はるかに管理しやすい状態に分解する方法を見てきました。この種の考え方を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がもたらす最大の利点は、状態を明示的にすることです。

各アニメーションはシステムを包含する状態に寄与する可能性がありますが、システムが一度に2つの状態になることはなく、状態がまったくないこともあります。また、システム(または各サブシステム)の状態を常に確認できるため、デバッグはほとんど簡単になります。あなたの州のデザインが理にかなっているとすれば。

また、状態を明示的に設計するように強制することにより、FSMを使用すると、状態/アクションの特定の組み合わせを考えない可能性が排除されます。すべての遷移は明示的であり、FSM設計の一部であるため、「未定義の動作」はありません。


ここまで読んだことがあれば、Additive Animations別のイントロ)に興味があるかもしれません。これらは現在iOS8のデフォルトであり、KevinDoughtyによって数年前から提唱されています。

これは別のアプローチであり、システムをステートフルに保ちながら、複数の(反対の)アニメーションを同時にアクティブにすることができます。これはあなたにクレイジーな結果を与えることができますが、それは興味深い概念です。

主なアイデアは、アニメーションを絶対値Aから絶対値Bに変化するものとして定義することを避け、代わりにアニメーションを最終値に相対的なものとして定義することです(各アニメーションは-Deltaから0に変化します)。これにより、各時点での相対値を合計することで複数のアニメーションをシームレスに組み合わせることができ、反転またはキャンセルによって引き起こされるスパイクを回避できます。

付加的なアニメーション
(出典:ronnqvi.st

ベアボーンフレームワークにとらわれない加法アニメーションの例については、alexkuzの加法アニメーションモジュール(デモ)を確認してください。


これまで読んだことがあるなら、あなたは本当にアニメーションに興味があるに違いありません!現在、私はreact-state-streamアプローチに興味を持っています。アニメーションを怠惰な状態のシーケンスとして表現することを提案します。これにより、無限のアニメーションを表現したり、アニメーションに変換を徐々に追加したり削除したりするなど、多くの可能性が開かれます。

アニメーションに関する1つの記事を読みたい場合は、ChengLouによるアニメーションについての考えをお勧めします。

于 2012-06-01T19:26:54.383 に答える