使用彈簧物理特性以動畫呈現動作

嘗試 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")
            }
            

    如要查看這個程式庫的目前版本,請參閱版本頁面中的動態動畫相關資訊。

建立彈簧效果

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:代表檢視畫面上的 Alpha 透明度。預設值為 1 (不透明),預設值為 0 代表完全透明 (不會顯示)。
  • TRANSLATION_XTRANSLATION_YTRANSLATION_Z:這些屬性可控制檢視畫面所在的位置,也就是從左座標、頂端座標和海拔高度設定 (透過版面配置容器設定)。
  • ROTATIONROTATION_XROTATION_Y:這些屬性可控制樞紐點的 2D (rotation 屬性) 和 3D 的旋轉情形。
  • SCROLL_XSCROLL_Y:這些屬性表示來源左側與上邊緣的捲動偏移量 (以像素為單位)。這也會指出頁面捲動多少的位置。
  • SCALE_XSCALE_Y:這些屬性可控制檢視畫面圍繞著樞紐點的 2D 縮放比例。
  • XYZ:這些是基本的公用程式屬性,用來描述檢視畫面在其容器中的最終位置。

註冊事件監聽器

DynamicAnimation 類別提供兩個事件監聽器: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() 方法並傳遞動畫的起始值。如未設定起始值,動畫會使用物件屬性目前的值做為起始值。

設定動畫值範圍

如果您想要將屬性值限制在特定範圍,可以設定最小和最大動畫值。此外,您還可以為具有內建範圍的屬性 (例如 Alpha 值 (從 0 到 1)) 建立動畫,藉此控制這個範圍。

  • 如要設定最小值,請呼叫 setMinValue() 方法並傳遞屬性值的最小值。
  • 如要設定最大值,請呼叫 setMaxValue() 方法並傳遞屬性值的最大值。

這兩種方法都會傳回要設定該值的動畫。

注意:如果您已設定起始值並定義動畫值範圍,請確保起始值在最小值和最大值範圍內。

設定啟動速度

「開始速度」是指動畫屬性在動畫開始時變更的速度。預設的啟動速度設為每秒 0 像素。您可以透過觸控手勢的速率,或使用固定值做為啟動速度,設定速率。如果選擇提供固定值,建議您以每秒 dp 為單位來定義值,然後再轉換為每秒像素數。以每秒 dp 為單位定義值,可讓速率與密度和板型規格無關。如要進一步瞭解如何將值轉換為每秒像素數,請參閱將每秒 dp 轉換為每秒像素一節。

如要設定速率,請呼叫 setStartVelocity() 方法,並傳遞每秒像素的速率 (以像素為單位)。這個方法會傳回已設定速率的彈簧力物件。

注意:請使用 GestureDetector.OnGestureListenerVelocityTracker 類別方法,擷取及計算觸控手勢的速度。

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 類別會定義每個彈簧屬性的 getter 和 setter 方法,例如阻尼比和硬度。如要設定彈簧屬性,請務必擷取彈簧力物件,或是建立能夠設定屬性的自訂彈力物件。如要進一步瞭解如何建立自訂彈簧,請參閱「建立自訂彈簧力」一節。

提示:使用 setter 方法時,您可以建立方法鏈結,因為所有 setter 方法都會傳回彈簧力物件。

阻尼比

阻尼比是指春天波動是逐漸降低的。使用阻尼比,您可以定義從一次跳轉到下次跳轉速度的衰減速度。有四種不同的方法可以消滅春季:

  • 阻隔率大於 1 時,就會發生過度取樣的情形。如此一來,物件就能輕輕地回到剩下的位置。
  • 阻隔率等於 1 時,會發生嚴重阻斷。可讓物件在最短時間內恢復為其餘位置。
  • 阻隔率小於 1 時,就會發生不足區域。它會傳遞其餘位置,然後逐漸達到剩餘位置,讓物件多次過度拍攝。
  • 阻隔率等於零時,就會發生未取樣情形。可讓物件永久閃爍。

如要將阻尼比加入彈簧,請按照下列步驟操作:

  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() 方法會執行兩項工作:

  • 設定彈簧的最終位置。
  • 如果動畫尚未開始,開始播放。

由於這個方法會更新彈簧的最終位置,並在需要時啟動動畫,因此您可以隨時呼叫此方法來變更動畫的階段。舉例來說,在鏈結彈簧動畫中,一個檢視畫面的動畫會依附另一個檢視畫面。針對這類動畫,使用 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();
…

取消動畫

您可以取消或跳至動畫結尾。有時候,當使用者互動要求立即終止動畫時,您必須取消或跳至修改結束。這主要是因為使用者突然離開應用程式,或是檢視畫面不再可見。

您有兩個方法可以用來終止動畫。cancel() 方法會根據顯示的值終止動畫。skipToEnd() 方法會略過動畫到最終值,然後終止動畫。

如要終止動畫,請務必先檢查彈簧的狀態。如果狀態遭到截斷,動畫就無法達到剩餘位置。如要檢查彈簧的狀態,請呼叫 canSkipToEnd() 方法。如果春季獲得阻尼,這個方法會傳回 true,否則會傳回 false

瞭解彈簧的狀態後,您可以使用 skipToEnd() 方法或 cancel() 方法終止動畫。cancel() 方法「必須」只能在主執行緒上呼叫。

注意:一般而言,skipToEnd() 方法會導致視覺跳轉。