溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android?Compose衰減動畫Animatable怎么使用

發(fā)布時間:2022-12-01 10:09:48 來源:億速云 閱讀:102 作者:iii 欄目:開發(fā)技術

這篇文章主要講解了“Android Compose衰減動畫Animatable怎么使用”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Android Compose衰減動畫Animatable怎么使用”吧!

animateDecay

首先還是來看一下 animateDecay的定義:

suspend fun animateDecay(
    initialVelocity: T,
    animationSpec: DecayAnimationSpec<T>,
    block: (Animatable<T, V>.() -> Unit)? = null
): AnimationResult<T, V>

跟前面介紹的 animateTo和 snapTo一樣都是 suspend修飾的方法,即必須在協(xié)程中調用,參數有三個,分別解析如下:

  • initialVelocity:初始速度

  • animationSpec:動畫配置,DecayAnimationSpec類型

  • block:函數類型參數,動畫運行的每一幀都會回調這個 block 方法,可用于動畫監(jiān)聽

返回值跟 animateTo一樣都是 AnimationResult類型。

initialVelocity是動畫的初始速度,動畫會從這個初始速度按照一定的衰減曲線進行衰減,直到速度為 0 或達到閾值時動畫停止。那這個初始速度的單位是多少呢?是單位/秒 這里的單位就是動畫作用的數值類型,比如數值類型是 Dp,那就代表多少 Dp 每秒。

而衰減曲線的配置就是第二個參數 animationSpec,需要注意的是這里的 animationSpec 是 DecayAnimationSpec類型,它并不是前面介紹的 AnimationSpec的子類,是衰減動畫特有的動畫配置,看一下 DecayAnimationSpec 的定義:

interface DecayAnimationSpec<T> {
    fun <V : AnimationVector> vectorize(
        typeConverter: TwoWayConverter<T, V>
    ): VectorizedDecayAnimationSpec<V>
}

從源碼可以知曉,DecayAnimationSpec是一個獨立的接口,跟蹤其實現類只有一個 DecayAnimationSpecImpl:

private class DecayAnimationSpecImpl<T>(
    private val floatDecaySpec: FloatDecayAnimationSpec
) : DecayAnimationSpec<T> {
    override fun <V : AnimationVector> vectorize(
        typeConverter: TwoWayConverter<T, V>
    ): VectorizedDecayAnimationSpec<V> = VectorizedFloatDecaySpec(floatDecaySpec)
}

這個實現類是 private的,也就是不能直接創(chuàng)建其實例,那怎么創(chuàng)建呢?Compose 提供三個方法用于創(chuàng)建,分別是 splineBasedDecay、rememberSplineBasedDecay和 exponentialDecay,那么這三種方法又有什么區(qū)別呢?下面分別對其進行詳細介紹。

splineBasedDecay

splineBasedDecay根據方法命名我們可以翻譯為基于樣條曲線的衰減,什么是樣條曲線呢?Google得到的答案:樣條曲線是經過或接近影響曲線形狀的一系列點的平滑曲線。更抽象了,實際上我們并不需要了解他是怎么實現的,當然感興趣的可以自行查詢相關資料,我們只要知道在 Android 中默認的列表慣性滑動就是基于此曲線算法實現的。

概念了解清楚后,再來看一下 splineBasedDecay 方法的定義:

fun <T> splineBasedDecay(density: Density): DecayAnimationSpec<T>

只有一個參數 density即屏幕像素密度。為什么要傳 density 呢?這是因為 splineBasedDecay 是基于屏幕像素進行的動畫速度衰減,當像素密度越大動畫減速越快,動畫的時長越短,動畫慣性滑動的距離越短;可以理解屏幕像素密度越大摩擦力越大,所以慣性滑動的距離就越短。

使用 splineBasedDecay 實現動畫效果,代碼如下:

// 創(chuàng)建 Animatable 實例
val animatable = remember { Animatable(10.dp, Dp.VectorConverter) }
val scope = rememberCoroutineScope()
// 創(chuàng)建 splineBasedDecay
// 通過 LocalDensity.current 獲取當前設備屏幕密度
val splineBasedDecay = splineBasedDecay<Dp>(LocalDensity.current)
Box(
    Modifier
        .padding(start = 10.dp, top = animatable.value)
        .size(100.dp, 100.dp)
        .background(Color.Blue)
        .clickable {
            scope.launch {
                // 啟動衰減動畫,初始速度設置為 1000.dp 每秒
                animatable.animateDecay(1000.dp, splineBasedDecay)
            }
        }
)

將上述代碼分別在屏幕尺寸均為 6.0 英寸、屏幕密度分別為 440 dpi 和 320 dpi 的設備上運行

可以發(fā)現,屏幕密度小的動畫運行的距離更長。

rememberSplineBasedDecay

rememberSplineBasedDecay 跟 splineBasedDecay 的作用是一樣的,區(qū)別在 splineBasedDecay 上用 remember包裹了一層,上一節(jié)中使用 splineBasedDecay 并未用 remember包裹,就意味著每次界面刷新時都會重新調用 splineBasedDecay 創(chuàng)建衰減配置的實例。而使用 rememberSplineBasedDecay就可以優(yōu)化該問題,且無需手動傳入 density參數。

看一下 rememberSplineBasedDecay源碼:

@Composable
actual fun <T> rememberSplineBasedDecay(): DecayAnimationSpec<T> {
    val density = LocalDensity.current
    return remember(density.density) {
        SplineBasedFloatDecayAnimationSpec(density).generateDecayAnimationSpec()
    }
}

首先也是通過 LocalDensity.current獲取屏幕像素密度,然后使用 remember創(chuàng)建衰減配置實例,remember參數傳入了 density,也就是當特殊情況屏幕密度發(fā)生變化時會重新創(chuàng)建衰減配置實例。

在開發(fā)中遇到要使用 splineBasedDecay的時候一般直接使用 rememberSplineBasedDecay 即可。

思考:前面介紹 splineBasedDecay 是跟屏幕像素密度有關的,如果需求就是不想因為屏幕像素密度而導致不同設備表現不一樣怎么辦呢?或者動畫作用的數值就是跟屏幕像素密度沒關,比如作用于旋轉角度的動畫,此時怎么辦呢?這個時候就不能使用 splineBasedDecay,而是應該使用 exponentialDecay。

exponentialDecay

exponentialDecay是指數衰減,即動畫速度按指數遞減,他不依賴屏幕像素密度,可用于通用數據的衰減動畫。其定義如下:

fun <T> exponentialDecay(
    frictionMultiplier: Float = 1f,
    absVelocityThreshold: Float = 0.1f
): DecayAnimationSpec<T>

有兩個參數,且都有默認值,參數解析如下:

  • frictionMultiplier:摩擦系數,摩擦系數越大,速度減速越快,反之則減速越慢

  • absVelocityThreshold:絕對速度閾值,當速度絕對值低于此值時動畫停止,這里的數值是指多少單位的速度,比如動畫數值類型為 Dp,這里傳 100f 即 100f * 1.dp

使用如下:

 var move by remember { mutableStateOf(false) }
    val animatable = remember { Animatable(30.dp, Dp.VectorConverter) }
    val scope = rememberCoroutineScope()
    Box(
        Modifier
            .padding(start = 30.dp, top = animatable.value)
            .size(100.dp, 100.dp)
            .background(Color.Blue)
            .clickable {
                scope.launch {
                    // 使用 exponentialDecay 衰減動畫
                    animatable.animateDecay(1000.dp, exponentialDecay())
                }
            }
    )

將摩擦系數設置為 5f 體驗一下增加摩擦系數后的效果:

exponentialDecay(5f)

摩擦系數增大后,動畫運行的距離和時間都明顯縮短了。

將絕對速度閾值設置為 500f 再看一下效果:

exponentialDecay(absVelocityThreshold = 500f)

當動畫速度達到閾值速度后動畫就停止了,所以閾值越大動畫越早停止。

實戰(zhàn)

下面我們用衰減動畫實現一個轉盤抽獎的動畫效果,即當點擊抽獎后轉盤開始轉動然后緩緩停下,最后指針指向的位置就是中獎的獎品。

因為是旋轉動畫,所以這里我們使用 exponentialDecay指數衰減動畫

將兩張圖片居中疊加,然后通過動畫旋轉下面的圓盤就完成了整個動畫效果,代碼如下:

// 創(chuàng)建動畫實例
val animatable = remember { Animatable(0, Int.VectorConverter) }
// 獲取協(xié)程作用域用戶在按鈕點擊事件中開啟協(xié)程
val scope = rememberCoroutineScope()
// 中獎結果
var luckyResult by remember { mutableStateOf("") }
// 中獎項
val luckyItem = remember { arrayOf("50元紅包", "20元紅包","10元紅包","100-50券","小米藍牙耳機","謝謝參與") }
Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
    Box{
        // 底部圓盤圖片
        Image(
            painter = painterResource(R.drawable.bg),
            contentDescription = "bg",
            // 旋轉角度設置為動畫的值
            modifier = Modifier.rotate(animatable.value.toFloat())
        )
        // 中間指針圖片
        Image(
            painter = painterResource(R.drawable.center),
            contentDescription = "center",
            // 設置點擊事件
            modifier = Modifier.clickable(indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = {
                // 開啟協(xié)程
                scope.launch {
                    // 更新抽獎狀態(tài)
                    luckyResult = "抽獎中"
                    // 開啟動畫
                    // 初始速度設置為 10000 再加上 1000~10000 的隨機數
                    // 衰減曲線設置為 exponentialDecay  摩擦系數設置為 0.5f
                    val result = animatable.animateDecay(10000 + Random.nextInt(1000,10000), exponentialDecay(frictionMultiplier = 0.5f))
                    // 動畫執(zhí)行完后從動畫結果中獲取最后的值,即旋轉角度
                    val angle = result.endState.value
                    // 通過計算獲取當前指針在哪個范圍
                    val index = angle % 360 / 60
                    // 獲取中獎結果,并顯示在屏幕上
                    luckyResult = luckyItem[index]
                }
            })
        )
    }
    // 顯示中獎結果
    Text(luckyResult, modifier = Modifier.padding(10.dp))
    // 添加重置按鈕
    Button(onClick = {
        scope.launch {
            // 通過 snapTo 瞬間回到初始狀態(tài)
            animatable.snapTo(0)
        }
    }){
        Text("重置")
    }
}

感謝各位的閱讀,以上就是“Android Compose衰減動畫Animatable怎么使用”的內容了,經過本文的學習后,相信大家對Android Compose衰減動畫Animatable怎么使用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI