ばねの物理的性質を利用して動きをアニメーションにする

物理学ベースのモーションは、力に基づいて動きます。インタラクティビティと動きを導くそのような力の 1 つとして、ばねの力があります。ばねの力には、減衰と剛性という特性があります。スプリング ベースのアニメーションでは、各フレームに適用されるばねの力に基づいて値と速度が計算されます。

アプリのアニメーションを一方向のみ遅くする場合は、代わりに摩擦ベースのフリング アニメーションを使用することを検討してください。

スプリング アニメーションのライフサイクル

スプリング ベースのアニメーションでは、SpringForce を使用して、ばねの剛性、減衰率、最終位置をカスタマイズできます。アニメーションが開始されるとすぐに、ばねの力によって各フレームのアニメーション値と速度が更新されます。アニメーションは、ばねの力が平衡に達するまで続きます。

たとえば画面上でアプリアイコンをドラッグし、その後アイコンから指を離すと、目には見えないものの馴染みのある力によってアイコンが元の位置に戻ります。

図 1 は、同様のばねの効果を示しています。円の中央にあるプラス記号(+)は、タップ操作で加えられる力を示します。

ばねの解放
図 1. ばね解放効果

スプリング アニメーションをビルドする

アプリケーションのスプリング アニメーションをビルドする一般的な手順は次のとおりです。

次のセクションでは、スプリング アニメーションをビルドする一般的な手順について詳しく説明します。

サポート ライブラリを追加する

物理学ベースのサポート ライブラリを使用するには、次のようにサポート ライブラリをプロジェクトに追加する必要があります。

  1. アプリ モジュールの build.gradle ファイルを開きます。
  2. サポート ライブラリを dependencies セクションに追加します。

          dependencies {
              def dynamicanimation_version = "1.0.0"
              implementation 'androidx.dynamicanimation:dynamicanimation:$dynamicanimation_version'
          }
          

    このライブラリの現在のバージョンを表示するには、バージョンのページで Dynamicanimation に関する情報をご覧ください。

スプリング アニメーションを作成する

SpringAnimation クラスを使用すると、オブジェクトのスプリング アニメーションを作成できます。スプリング アニメーションを作成するには、SpringAnimation クラスのインスタンスを作成し、オブジェクト、アニメーション化するオブジェクトのプロパティ、アニメーションを停止させるばねの最終位置(オプション)を指定する必要があります。

注: スプリング アニメーションの作成時、ばねの最終位置はオプションです。ただし、アニメーションを開始する前に定義する必要があります

Kotlin

    val springAnim = findViewById<View>(R.id.imageView).let { img ->
        // Setting up a spring animation to animate the view’s translationY property with the final
        // spring position at 0.
        SpringAnimation(img, DynamicAnimation.TRANSLATION_Y, 0f)
    }
    

Java

    final View img = findViewById(R.id.imageView);
    // Setting up a spring animation to animate the view’s translationY property with the final
    // spring position at 0.
    final SpringAnimation springAnim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y, 0);
    

スプリング ベースのアニメーションでは、ビュー オブジェクトの実際のプロパティを変更することで、画面上のビューをアニメーション化できます。システムで使用できるビューは次のとおりです。

  • ALPHA: ビューのアルファ透明度を表します。デフォルトの値は 1(不透明)で、値 0 は完全な透明(非表示)を表します。
  • TRANSLATION_XTRANSLATION_YTRANSLATION_Z: これらのプロパティは、レイアウト コンテナで設定されている左座標、上座標、高さからの差分として、ビューの位置を制御します。
  • ROTATIONROTATION_XROTATION_Y: これらのプロパティは、ピボット ポイントを中心とした 2D の回転(rotation プロパティ)と 3D の回転を制御します。
  • SCROLL_XSCROLL_Y: これらのプロパティは、ソースの左端と上端のスクロール オフセットをピクセル単位で示します。また、ページのスクロール量を位置で示します。
  • SCALE_XSCALE_Y: これらのプロパティは、ピボット ポイントを中心としたビューの 2D スケーリングを制御します。
  • XYZ: コンテナ内のビューの最終位置を示す基本的なユーティリティ プロパティです。

リスナーを登録する

DynamicAnimation クラスは、2 つのリスナー(OnAnimationUpdateListenerOnAnimationEndListener)を提供します。これらのリスナーは、アニメーション値の変更やアニメーションの終了など、アニメーションの更新をリッスンします。

OnAnimationUpdateListener

複数のビューをアニメーション化してチェーン アニメーションを作成する場合は、現在のビューのプロパティに変更があるたびにコールバックを受け取るように OnAnimationUpdateListener を設定できます。コールバックは、現在のビューのプロパティで発生した変更に基づいて、ばねの位置を更新するように、他のビューに通知します。リスナーを登録する手順は次のとおりです。

  1. addUpdateListener() メソッドを呼び出し、リスナーをアニメーションにアタッチします。

    注: アニメーションを開始する前に、更新リスナーを登録する必要があります。ただし、更新リスナーは、アニメーション値の変更に関してフレーム単位での更新が必要な場合にのみ登録する必要があります。更新リスナーは、アニメーションが別のスレッドで実行される可能性を防止します。

  2. onAnimationUpdate() メソッドをオーバーライドして、現在のオブジェクトの変更を呼び出し元に通知します。次のサンプルコードは、OnAnimationUpdateListener の全体的な使用方法を示しています。

Kotlin

    // Setting up a spring animation to animate the view1 and view2 translationX and translationY properties
    val (anim1X, anim1Y) = findViewById<View>(R.id.view1).let { view1 ->
        SpringAnimation(view1, DynamicAnimation.TRANSLATION_X) to
                SpringAnimation(view1, DynamicAnimation.TRANSLATION_Y)
    }
    val (anim2X, anim2Y) = findViewById<View>(R.id.view2).let { view2 ->
        SpringAnimation(view2, DynamicAnimation.TRANSLATION_X) to
                SpringAnimation(view2, DynamicAnimation.TRANSLATION_Y)
    }

    // Registering the update listener
    anim1X.addUpdateListener { _, value, _ ->
        // Overriding the method to notify view2 about the change in the view1’s property.
        anim2X.animateToFinalPosition(value)
    }

    anim1Y.addUpdateListener { _, value, _ -> anim2Y.animateToFinalPosition(value) }
    

Java

    // Creating two views to demonstrate the registration of the update listener.
    final View view1 = findViewById(R.id.view1);
    final View view2 = findViewById(R.id.view2);

    // Setting up a spring animation to animate the view1 and view2 translationX and translationY properties
    final SpringAnimation anim1X = new SpringAnimation(view1,
            DynamicAnimation.TRANSLATION_X);
    final SpringAnimation anim1Y = new SpringAnimation(view1,
        DynamicAnimation.TRANSLATION_Y);
    final SpringAnimation anim2X = new SpringAnimation(view2,
            DynamicAnimation.TRANSLATION_X);
    final SpringAnimation anim2Y = new SpringAnimation(view2,
            DynamicAnimation.TRANSLATION_Y);

    // Registering the update listener
    anim1X.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {

    // Overriding the method to notify view2 about the change in the view1’s property.
        @Override
        public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float value,
                                      float velocity) {
            anim2X.animateToFinalPosition(value);
        }
    });

    anim1Y.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {

      @Override
        public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float value,
                                      float velocity) {
            anim2Y.animateToFinalPosition(value);
        }
    });
    

OnAnimationEndListener

OnAnimationEndListener は、アニメーションの終了を通知します。アニメーションが平衡に達するかキャンセルされときにコールバックを受け取るように、リスナーを設定できます。リスナーを登録する手順は次のとおりです。

  1. addEndListener() メソッドを呼び出し、リスナーをアニメーションにアタッチします。
  2. onAnimationEnd() メソッドをオーバーライドして、アニメーションが平衡に達するかキャンセルされたときに通知を受け取るようにします。

リスナーを削除する

アニメーション更新コールバックとアニメーション終了コールバックの受信を停止するには、それぞれ removeUpdateListener() メソッドと removeEndListener() メソッドを呼び出します。

アニメーション開始値を設定する

アニメーションの開始値を設定するには、setStartValue() メソッドを呼び出し、アニメーションの開始値を渡します。開始値を設定しない場合、アニメーションはオブジェクトのプロパティの現在値を開始値として使用します。

アニメーションの値の範囲を設定する

プロパティ値を特定の範囲に制限する場合は、アニメーションの最小値と最大値を設定できます。また、アルファ(0 から 1)などの固有の範囲を持つプロパティをアニメーション化する場合にも、範囲の制御に役立ちます。

  • 最小値を設定するには、setMinValue() メソッドを呼び出し、プロパティの最小値を渡します。
  • 最大値を設定するには、setMaxValue() メソッドを呼び出し、プロパティの最大値を渡します。

どちらのメソッドも、値が設定されているアニメーションを返します。

注: 開始値を設定し、アニメーション値の範囲を定義した場合は、開始値が最小値と最大値の範囲内にあることを確認してください。

開始速度を設定する

開始速度は、アニメーションの開始時にアニメーション プロパティが変更される速さを定義します。デフォルトの開始速度は 0 ピクセル/秒に設定されています。タップ操作の速度を使用するか、開始速度として固定値を使用することで、速度を設定できます。固定値を指定する場合は、dp/秒で値を定義してからピクセル/秒に変換することをおすすめします。dp/秒で値を定義すると、速度は密度とフォーム ファクタに依存しなくなります。値をピクセル/秒に変換する方法の詳細については、dp/秒をピクセル/秒に変換するのセクションをご覧ください。

速度を設定するには、setStartVelocity() メソッドを呼び出し、ピクセル/秒で速度を渡します。このメソッドは、速度が設定されているばねの力のオブジェクトを返します。

注: タップ操作の速度の取得と計算を行うには、GestureDetector.OnGestureListener または VelocityTracker クラスメソッドを使用します。

Kotlin

    findViewById<View>(R.id.imageView).also { img ->
        SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
            …
            // Compute velocity in the unit pixel/second
            vt.computeCurrentVelocity(1000)
            val velocity = vt.yVelocity
            setStartVelocity(velocity)
        }
    }
    

Java

    final View img = findViewById(R.id.imageView);
    final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
    …
    // Compute velocity in the unit pixel/second
    vt.computeCurrentVelocity(1000);
    float velocity = vt.getYVelocity();
    anim.setStartVelocity(velocity);
    

dp/秒をピクセル/秒に変換する

ばねの速度は、ピクセル/秒である必要があります。速度の開始値として固定値を指定する場合は、dp/秒で値を指定してから、ピクセル/秒に変換します。変換には、TypedValue クラスの applyDimension() メソッドを使用します。次のサンプルコードをご覧ください。

Kotlin

    val pixelPerSecond: Float =
        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, resources.displayMetrics)
    

Java

    float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, getResources().getDisplayMetrics());
    

ばねの特性を設定する

SpringForce クラスは、ばねの各特性(減衰率や剛性など)のゲッター メソッドとセッター メソッドを定義します。ばねの特性を設定するには、ばねの力のオブジェクトを取得するか、特性を設定できるカスタムのばねの力を作成する必要があります。カスタムのばねの力を作成する方法の詳細については、カスタムのばねの力を作成するのセクションをご覧ください。

ヒント: セッター メソッドを使用している間は、すべてのセッター メソッドがばねの力のオブジェクトを返すため、メソッド チェーンを作成できます。

減衰率

減衰率は、ばねの振動の漸進的な減少を表します。減衰率を使用すると、ある跳ね返りから次の跳ね返りまでの、振動が減衰する速さを定義できます。ばねを減衰させる方法は 4 つあります。

  • 過減衰は、減衰率が 1 より大きい場合に発生します。オブジェクトを静止位置にすばやく戻せます。
  • 臨界減衰は、減衰率が 1 に等しい場合に発生します。オブジェクトを静止位置に最短時間で戻せます。
  • 不足減衰は、減衰比が 1 未満の場合に発生します。オブジェクトが静止位置を通過して複数回オーバーシュートし、徐々に静止位置に達します。
  • 非減衰は、減衰率が 0 に等しい場合に発生します。オブジェクトを永続的に振動させられます。

減衰率をばねに追加する手順は次のとおりです。

  1. getSpring() メソッドを呼び出してばねを取得し、減衰率を追加します。
  2. setDampingRatio() メソッドを呼び出し、ばねに追加する減衰率を渡します。このメソッドは、減衰率が設定されているばねの力のオブジェクトを返します。

    注: 減衰率は負でない数である必要があります。減衰率を 0 に設定した場合、ばねが静止位置に達することはありません。つまり、永続的に振動します。

システムで使用できる減衰率定数は次のとおりです。

図 2: 高反発

図 3: 中反発

図 4: 低反発

図 5: 無反発

デフォルトの減衰率は DAMPING_RATIO_MEDIUM_BOUNCY に設定されています。

Kotlin

    findViewById<View>(R.id.imageView).also { img ->
        SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
            …
            //Setting the damping ratio to create a low bouncing effect.
            spring.dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY
            …
        }
    }
    

Java

    final View img = findViewById(R.id.imageView);
    final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
    …
    //Setting the damping ratio to create a low bouncing effect.
    anim.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
    …
    

剛性

剛性は、ばねの強さを測るばね定数を定義します。硬いばねは、ばねが静止位置にないとき、アタッチされているオブジェクトに大きな力を加えます。剛性をばねに追加する手順は次のとおりです。

  1. getSpring() メソッドを呼び出してばねを取得し、剛性を追加します。
  2. setStiffness() メソッドを呼び出し、ばねに追加する剛性値を渡します。このメソッドは、剛性が設定されているばねの力のオブジェクトを返します。

    注: 剛性は正の数である必要があります。

システムで使用できる剛性定数は次のとおりです。

図 6: 高剛性

図 7: 中剛性

図 8: 低剛性

図 9: 超低剛性

デフォルトの剛性は STIFFNESS_MEDIUM に設定されています。

Kotlin

    findViewById<View>(R.id.imageView).also { img ->
        SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
            …
            //Setting the spring with a low stiffness.
            spring.stiffness = SpringForce.STIFFNESS_LOW
            …
        }
    }
    

Java

    final View img = findViewById(R.id.imageView);
    final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
    …
    //Setting the spring with a low stiffness.
    anim.getSpring().setStiffness(SpringForce.STIFFNESS_LOW);
    …
    

カスタムのばねの力を作成する

デフォルトのばねの力を使用する代わりに、カスタムのばねの力を作成できます。カスタムのばねの力を使用すると、複数のスプリング アニメーションで同じばねの力のインスタンスを共有できます。ばねの力を作成したら、減衰率や剛性などの特性を設定できます。

  1. SpringForce オブジェクトを作成します。

    SpringForce force = new SpringForce();

  2. 各メソッドを呼び出してプロパティを割り当てます。メソッド チェーンを作成することもできます。

    force.setDampingRatio(DAMPING_RATIO_LOW_BOUNCY).setStiffness(STIFFNESS_LOW);

  3. setSpring() メソッドを呼び出して、ばねをアニメーションに設定します。

    setSpring(force);

アニメーションを開始する

スプリング アニメーションを介しする方法は 2 つあります。start() をメソッドを呼び出す方法と、animateToFinalPosition() メソッドを呼び出す方法です。どちらのメソッドも、メインスレッドで呼び出す必要があります。

animateToFinalPosition() メソッドは、タスクを 2 つ実行します。

  • ばねの最終位置を設定します。
  • アニメーションが開始されていない場合、開始します。

このメソッドは、ばねの最終位置を更新し、必要に応じてアニメーションを開始するため、アニメーションの経過をいつでも変更できます。たとえばチェーン スプリング アニメーションでは、あるビューのアニメーションが別のビューに依存します。このようなアニメーションには、animateToFinalPosition() メソッドを使用すると便利です。チェーン スプリング アニメーションでこのメソッドを使用すると、次に更新するアニメーションが現在実行中であるかどうかを心配せずに済みます。

図 10 は、チェーン スプリング アニメーションを示しています。あるビューのアニメーションが別のビューに依存しています。

チェーン スプリングのデモ
図 10. チェーン スプリングのデモ

animateToFinalPosition() メソッドを使用するには、animateToFinalPosition() メソッドを呼び出し、ばねの静止位置を渡します。setFinalPosition() メソッドを呼び出すことで、ばねの静止位置を設定することもできます。

start() メソッドは、プロパティ値をすぐに開始値に設定するわけではありません。プロパティ値はアニメーション パルスごとに変化します。これは描画パスの前に発生します。その結果、値がすぐに設定されたかのように、変更が次のフレームに反映されます。

Kotlin

    findViewById<View>(R.id.imageView).also { img ->
        SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
            …
            //Starting the animation
            start()
            …
        }
    }

    

Java

    final View img = findViewById(R.id.imageView);
    final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
    …
    //Starting the animation
    anim.start();
    …
    

アニメーションをキャンセルする

アニメーションをキャンセルまたは最後までスキップできます。アニメーションをキャンセルまたは最後までスキップする必要がある状況としては、ユーザー操作によってアニメーションをすぐに終了する必要がある場合が考えられます。これは主に、ユーザーがアプリを突然終了した場合、またはビューが非表示になった場合です。

アニメーションを終了するために使用できるメソッドは 2 つあります。cancel() メソッドは、アニメーションを指定した位置の値で終了します。skipToEnd() メソッドは、アニメーションを最後の値にスキップして終了します。

アニメーションを終了する前に、ばねの状態を最初に確認することが重要です。状態が非減衰の場合、アニメーションが静止位置に達することはありません。ばねの状態を確認するには、canSkipToEnd() メソッドを呼び出します。ばねが減衰している場合、このメソッドは true を返し、それ以外の場合は false を返します。

ばねの状態がわかったら、skipToEnd() メソッドまたは cancel() メソッドを使用してアニメーションを終了できます。cancel() メソッドは、メインスレッドでのみ呼び出す必要があります

注: 一般に、skipToEnd() メソッドでは表示のジャンプが発生します。