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

Compose を試す
Jetpack Compose は、Android に推奨される UI ツールキットです。Compose でアニメーションを使用する方法を学習します。

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

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

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

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

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

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

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

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

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

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

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

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

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

    Groovy

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

    Kotlin

            dependencies {
                val 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 クラスには、OnAnimationUpdateListenerOnAnimationEndListener の 2 つのリスナーが用意されています。これらのリスナーは、アニメーション値が変更されたときや、アニメーションが終了したときなど、アニメーションの更新をリッスンします。

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/秒で値を定義すると、速度を密度やフォーム ファクタに依存させることができます。値を 1 秒あたりのピクセル数に変換する方法については、1 秒あたりの dp を 1 秒あたりのピクセル数に変換するをご覧ください。

速度を設定するには、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() メソッドを呼び出し、ばねに追加する減衰率を渡します。このメソッドは、減衰率が設定されているばねの力のオブジェクトを返します。

    注: 減衰率は負でない数値でなければなりません。減衰率をゼロに設定すると、ばねが静止位置に達することはありません。つまり、永久に振動しているということです。

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

図 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);

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

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

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

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

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

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

チェーン スプリングのデモ
図 10.チェーンされた Spring のデモ

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() メソッドを使うと表示が切り替わります。