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

試試 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: 代表檢視區塊的 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() 方法,並傳遞屬性的最大值。

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

注意:如果已設定起始值並定義動畫值範圍,請確保起始值介於最小值和最大值範圍之間。

設定初速度

起始速度會定義動畫屬性在動畫開始時的變化速度。預設的起始速度為每秒零像素。你可以使用觸控手勢的速度設定速度,也可以使用固定值做為起始速度。如果選擇提供固定值,建議您以每秒 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());

設定 Spring 屬性

SpringForce 類別會為每個彈簧屬性 (例如阻尼比和勁度) 定義 getter 和 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 說明鏈結彈簧效果,其中一個檢視區塊的動畫取決於另一個檢視區塊。

Chained spring demo
圖 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() 方法會造成視覺跳動。