Android Compose 動畫使用詳解(六)動畫配置之SpringSpec

語言: CN / TW / HK

theme: smartblue

本文為稀土掘金技術社群首發簽約文章,14天內禁止轉載,14天后未獲授權禁止轉載,侵權必究!

前言

前面文章介紹到的 TweenSpecSnapSpecKeyframesSpace都是實現自 DurationBasedAnimationSpec即動畫時長都是確定的,看到這裡可能有些同學就有疑問了?那難道還有動畫時長不確定的動畫麼?是的,比如本篇就要介紹的 SpringSpec

SpringSpec的 Spring 在這裡是彈簧的意思,即彈簧動畫規格配置,本篇就帶你詳細瞭解其各引數的含義和使用。

SpringSpec

上面說SpringSpec是彈簧動畫,那在現實世界中彈簧到底是怎麼運動的呢?下面用一張示意圖展示一下:

當彈簧被拉離原點放開後,彈簧會彈回原點然後在原點附近震盪數次後最終在原點停止,這就是彈簧動畫效果,即動畫運動到目標值後不會立即停止,而是會按照彈簧特性在目標值附近震盪數次後再停止在目標值,而震盪的次數和速度就是SpringSpec需要配置的引數。

來看一下 SpringSpec的定義,程式碼如下:

class SpringSpec<T>( val dampingRatio: Float = Spring.DampingRatioNoBouncy, val stiffness: Float = Spring.StiffnessMedium, val visibilityThreshold: T? = null ) : FiniteAnimationSpec<T>

SpringSpec構造方法有三個引數,且都有預設值,引數解析如下:

  • dampingRatio:彈簧阻尼比,數值越小震盪的次數越多
  • stiffness:彈簧剛度,剛度越大彈簧回到原點的速度越快,即動畫執行得越快
  • visibilityThreshold:可視閾值,即當彈簧彈到某一個值的時候就不彈了然後直接運動到目標值

可通過構造方法或簡便函式 spring方法使用,引數都是一致的,如下:

``` // 構造方法使用 val springSpec = SpringSpec(dampingRatio = 0.1f, stiffness = 500f, visibilityThreshold = 0.01.dp)

// spring 簡便函式使用 val springSpec = spring(dampingRatio = 0.1f, stiffness = 500f, visibilityThreshold = 0.01.dp)

// 使用 animateDpAsState(targetValue, animationSpec = springSpec) ```

下面分別來介紹這三個引數的作用和效果。

dampingRatio

阻尼係數越大,彈性運動的震盪次數越少、震盪幅度越小,阻尼係數有 4 個區間值,分別如下:

  • dampingRatio = 0:無阻尼,彈簧處於永遠震盪的狀態
  • dampingRatio < 1:欠阻尼,彈簧進行指數遞減的震盪運動
  • dampingRatio = 1:臨界阻尼,彈簧以最短時間結束運動,無震盪運動
  • dampingRatio > 1:過阻尼,彈簧進行無震盪的減速運動

這裡實現一個小球下落的動畫效果示例,為了體現震盪的效果,在下落過程中同時增加小球向右的偏移量並用線條繪製出小球的運動軌跡,實現程式碼如下:

``` // 螢幕寬度 val screenWidth = LocalConfiguration.current.screenWidthDp.dp // 螢幕高度 val screenHeight = LocalConfiguration.current.screenHeightDp.dp // 繪製運動軌跡的點 val points = remember { mutableListOf() } // 球的大小 val ballSize = 50.dp // 目標位置 val target = 150.dp

// 控制動畫的狀態 var moveToRight by remember { mutableStateOf(false) } // 根據狀態計算目標值 val targetValue = if(moveToRight) target else 10.dp

// 球距離頂部的位置 val topPadding by animateDpAsState(targetValue, animationSpec = spring()) // 球距離左邊的位置 var leftPadding by remember { mutableStateOf(10.dp) }

// 監聽 topPadding 的變化修改 leftPadding 值並新增軌跡點 val leftPaddingValue = remember(topPadding) { leftPadding += 1.dp points.add(DpOffset(leftPadding, topPadding)) leftPadding }

Box { // 目標位置線條 Box(modifier = Modifier.padding(top = target + ballSize/2 - 1.5.dp).size(screenWidth, 3.dp).background(Color.Green)) // 小球運動軌跡 Box(modifier = Modifier.size(screenWidth, screenHeight)){ Canvas(modifier = Modifier.fillMaxSize()) { val path = Path() path.moveTo(10.dp.toPx(), 10.dp.toPx()+(ballSize/2).toPx()) if(points.isNotEmpty()){ points.forEach { path.lineTo(it.x.toPx(), it.y.toPx()+(ballSize/2).toPx()) } drawPath( path = path, color = Color.Red, style = Stroke( width = 3.dp.toPx(), ) ) } } } // 小球 Box( Modifier // 設定左邊和頂部的間距 .padding(start = leftPaddingValue, top = topPadding) .size(ballSize, ballSize) .clip(RoundedCornerShape(25.dp)) .background(Color.Blue) .clickable { leftPadding = 10.dp points.clear() moveToRight = true } ) } ```

將上面程式碼中的 spring引數 dampingRatio 設定不同值的看看效果。

  1. dampingRatio = 0

val topPadding by animateDpAsState(targetValue, animationSpec = spring(dampingRatio = 0f))

效果:

咦?效果不對啊,上面不是說當阻尼比為 0 的時候是永遠處於震盪狀態嗎?怎麼上面的效果並沒有震盪效果呢?那是因為阻尼比為 0 是一種理論狀態,現實世界是不存在的,而 Compose 不支援設定阻尼比為 0,當設定為 0 時雖然程式碼不會報錯但執行實際是沒有彈簧效果的。

  1. dampingRatio < 1

val topPadding by animateDpAsState(targetValue, animationSpec = spring(dampingRatio = 0.1f))

這裡設定阻尼比為 0.1f 再來看一下效果:

震盪效果就很明顯了,小球震盪了很多次後才停在了目標位置。

  1. dampingRatio = 1

val topPadding by animateDpAsState(targetValue, animationSpec = spring(dampingRatio = 1f))

效果如下:

沒有震盪效果,小球減速運動到目標位置。

  1. dampingRatio > 1

val topPadding by animateDpAsState(targetValue, animationSpec = spring(dampingRatio = 10f))

效果:

跟設定 1 時一樣沒有震盪效果,但是執行速度更慢了,當大於 1 時值越大運動速度越慢,動畫時長越久。

實際上 Compose 預置了 4 個阻尼比值,如下:

``` object Spring {

// 震盪效果高
const val DampingRatioHighBouncy = 0.2f

// 震盪效果中
const val DampingRatioMediumBouncy = 0.5f

// 震盪效果低
const val DampingRatioLowBouncy = 0.75f

// 無震盪效果
const val DampingRatioNoBouncy = 1f

} ```

分別代表震盪效果的高、中、低和無震盪效果,預設為無震盪效果,在實際開發中我們一般使用這四種預置的阻尼比值就能實現大部分動畫效果,效果對比如下:

從上面的效果對比圖中可以看出來,當阻尼比越小時動畫震盪的次數越多、幅度越大,相反則震盪的次數越少、幅度越小,大於等於 1 時無震盪。

stiffness

stiffness是彈簧的剛度,剛度越大,抵抗彈簧變形的能力越強,回到原點位置的速度就越快,即動畫得越快。

同樣的 Compose 內建了 5 個剛度值,定義如下:

``` object Spring {

// 高剛度
const val StiffnessHigh = 10_000f

// 中剛度
const val StiffnessMedium = 1500f

// 中低剛度
const val StiffnessMediumLow = 400f

// 低剛度
const val StiffnessLow = 200f

// 非常低剛度
const val StiffnessVeryLow = 50f

} ```

將彈簧阻尼比設定為 DampingRatioHighBouncy時不同剛度值對應的效果如下:

在阻尼比相同的情況下不同剛度值對於動畫震盪的幅度和次數是一樣的,唯一不同的是速度,剛度值越高速度越快,動畫時長越短,相反則速度越慢,動畫時長越長。

visibilityThreshold

visibilityThreshold是動畫的可視閾值,在這裡的作用是當震盪幅度小於設定的可視閾值時動畫將停止震盪並直接運動到目標值。

從文字上理解可能比較抽象,我們還是以上面的效果為例,當阻尼比設定為 DampingRatioHighBouncy、剛度設定為 StiffnessVeryLow時,通過設定可視閾值分別為預設值 null 、0.1.dp、1.dp、10.dp、100.dp 來看一下效果:

可以發現,當可視閾值設定得越大時,動畫停止的越早。

實戰

接下來我們就用 SpringSpec 實現一個簡單的實戰動畫效果,一個點選圖片縮放動畫效果,使用SpringSpec來實現,程式碼如下:

``` // 目標大小 val target = 300.dp

// 縮放狀態 var big by remember { mutableStateOf(false) } // 根據狀態的目標值 val targetValue = if (big) target else 100.dp

// 基於動畫的圖片大小 val size by animateDpAsState( targetValue, animationSpec = spring( // 阻尼比:中 dampingRatio = Spring.DampingRatioMediumBouncy, // 剛度:低 stiffness = Spring.StiffnessLow ) )

Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Image( painter = painterResource(R.drawable.dog), contentDescription = "dog", modifier = Modifier .size(size) .clip(RoundedCornerShape(10.dp)) .clickable { // 改變狀態 big = !big }, contentScale = ContentScale.Crop )

} ```

最終動畫效果:

可以發現,在一個普通的圖片縮放動畫上加上彈簧動畫配置後,動畫的視覺體驗得到了成倍的增加,給使用者帶來了更好的體驗。

最後

本篇詳細介紹了彈簧動畫規格 SpringSpec的引數配置及對應的使用效果,並用一個簡單的實戰效果體驗了彈簧動畫的魅力,至此 6 種動畫規格配置已經探索了 4 個,下一篇我們將繼續探索最後兩個動畫規格:RepeatableSpec(重複動畫)、InfiniteRepeatableSpec(無限重複動畫),請持續關注本專欄瞭解更多 Compose 動畫相關內容。