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

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 秒あたりのピクセル数に変換する方法について詳しくは、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() メソッドを呼び出すかの 2 つの方法があります。どちらのメソッドもメインスレッドで呼び出す必要があります。

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() メソッドを使用すると、視覚的なジャンプが発生します。