欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android?Compose之Animatable動(dòng)畫(huà)停止使用詳解

 更新時(shí)間:2023年03月29日 11:50:34   作者:loongwind  
這篇文章主要為大家介紹了Android?Compose之Animatable動(dòng)畫(huà)停止使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

前面講了Animatable的基礎(chǔ)以及衰減動(dòng)畫(huà)的用法,本篇?jiǎng)t主要講解 Animatable 動(dòng)畫(huà)的停止,動(dòng)畫(huà)的停止情況主要分為四種,如下:

  • 動(dòng)畫(huà)正常運(yùn)行完成后停止
  • 動(dòng)畫(huà)被打斷停止
  • 主動(dòng)停止動(dòng)畫(huà)
  • 動(dòng)畫(huà)觸達(dá)邊界停止

第一種很好理解,就是動(dòng)畫(huà)按照我們?cè)O(shè)計(jì)的參數(shù)正常運(yùn)行完成的情況,這種情況屬于正常停止,此時(shí)動(dòng)畫(huà)返回結(jié)果的 endReasonAnimationEndReason.Finished(在《Android Compose 動(dòng)畫(huà)Animatable的使用》一文中有詳細(xì)介紹),本文主要介紹剩下三種停止情況。

打斷停止

顧名思義即動(dòng)畫(huà)在運(yùn)行過(guò)程中被打斷,那么是被什么打斷呢?被同一個(gè) Animatable的另一個(gè)動(dòng)畫(huà)打斷,簡(jiǎn)單的說(shuō)就是當(dāng) Animatable在執(zhí)行某一個(gè)動(dòng)畫(huà)的過(guò)程中,此時(shí)再使用同一個(gè) Animatable 去開(kāi)啟另一個(gè)動(dòng)畫(huà),此時(shí)就會(huì)打斷正在運(yùn)行中的動(dòng)畫(huà),即停止正在運(yùn)行中的話執(zhí)行新的動(dòng)畫(huà)。

那么為什么要打斷正在運(yùn)行中的動(dòng)畫(huà)呢?不能兩個(gè)動(dòng)畫(huà)一起執(zhí)行嗎?假設(shè)一個(gè)動(dòng)畫(huà)是將方塊移動(dòng)到 200dp 的位置,另一個(gè)動(dòng)畫(huà)是將方塊移動(dòng)到 0dp 的位置,如果不會(huì)被打斷兩個(gè)動(dòng)畫(huà)可以同時(shí)執(zhí)行,那就會(huì)出現(xiàn)方塊一會(huì)兒往 200dp 位置一會(huì)兒往 0dp 移動(dòng)的閃爍情況,顯然動(dòng)畫(huà)效果不符合預(yù)期,所以被設(shè)計(jì)成了后一個(gè)動(dòng)畫(huà)會(huì)打斷前一個(gè)動(dòng)畫(huà)。

下面就用一個(gè)實(shí)例演示動(dòng)畫(huà)的打斷,代碼如下:

// 方塊顏色,默認(rèn)為藍(lán)色
var backgroundColor by remember { mutableStateOf(Color.Blue) }
// 動(dòng)畫(huà)實(shí)例
val animatable = remember { Animatable(10.dp, Dp.VectorConverter) }
// 獲取協(xié)程作用域
val scope = rememberCoroutineScope()
Box {
    // 動(dòng)畫(huà)方塊
    Box(
        Modifier
            // 使用動(dòng)畫(huà)值
            .padding(start = animatable.value, top = 30.dp)
            .size(100.dp, 100.dp)
            .background(backgroundColor)
            .clickable { // 點(diǎn)擊事件
                // 啟動(dòng)動(dòng)畫(huà)
                scope.launch {
                    animatable.animateTo(200.dp,
                        // 為了方便看到效果,動(dòng)畫(huà)時(shí)間設(shè)置為 1000ms                 
                        animationSpec = tween(durationMillis = 1000)
                    )
                }
            }
    )
    // 按鈕,用于開(kāi)啟新動(dòng)畫(huà)
    Button(onClick = {
        // 修改方塊顏色,方便觀察區(qū)分兩個(gè)動(dòng)畫(huà)
        backgroundColor = Color.Cyan
        // 啟動(dòng)新動(dòng)畫(huà)
        scope.launch {
            animatable.animateTo(50.dp, animationSpec = tween(durationMillis = 1000))
        }
    }, Modifier.padding(top = 170.dp, start = 70.dp)) {
        Text(text = "Next", style = TextStyle(fontSize = 10.sp))
    }
}

界面上添加了一個(gè)方塊和一個(gè)按鈕,點(diǎn)擊方塊執(zhí)行動(dòng)畫(huà)移動(dòng)到 200dp 位置,點(diǎn)擊按鈕執(zhí)行動(dòng)畫(huà)移動(dòng)到 50dp 位置,為了區(qū)分兩個(gè)動(dòng)畫(huà)動(dòng)畫(huà)執(zhí)行時(shí)為方塊設(shè)置了不同的顏色,運(yùn)行效果如下:

從上面的效果可以看出,對(duì)同一個(gè) Animatable開(kāi)啟新的動(dòng)畫(huà)確實(shí)會(huì)打斷正在運(yùn)行的動(dòng)畫(huà)。

除了上面演示的 animateTo可以打斷動(dòng)畫(huà)以外,AnimatablesnapTo、animateDecay同樣可以打斷動(dòng)畫(huà)。

主動(dòng)停止

前面介紹的是新動(dòng)畫(huà)打斷正在運(yùn)行的動(dòng)畫(huà),那么如果我們想主動(dòng)停止一個(gè)Animatable動(dòng)畫(huà)該怎么辦呢?很簡(jiǎn)單,Animatable提供了stop方法用于停止動(dòng)畫(huà)。

示例代碼如下:

var backgroundColor by remember { mutableStateOf(Color.Blue) }
// 動(dòng)畫(huà)實(shí)例
val animatable = remember { Animatable(10.dp, Dp.VectorConverter) }
val scope = rememberCoroutineScope()
Box {
    // 動(dòng)畫(huà)方塊
    Box(
        Modifier
            // 使用動(dòng)畫(huà)值
            .padding(start = animatable.value, top = 30.dp)
            .size(100.dp, 100.dp)
            .background(backgroundColor)
            .clickable { // 點(diǎn)擊事件,開(kāi)啟動(dòng)畫(huà)
                scope.launch {
                    animatable.animateTo(200.dp,
                    	// 為了方便觀察動(dòng)畫(huà)效果,動(dòng)畫(huà)時(shí)長(zhǎng)設(shè)置為 1000ms
                        animationSpec = tween(durationMillis = 1000)
                    )
                }
            }
    )
    // 停止按鈕
    Button(onClick = {
        // 修改方塊顏色
        backgroundColor = Color.Cyan
        // 停止動(dòng)畫(huà)
        scope.launch {
            animatable.stop()
        }
    }, Modifier.padding(top = 170.dp, start = 70.dp)) {
        Text(text = "Stop", style = TextStyle(fontSize = 10.sp))
    }
}

需要注意的是 stop方法也是一個(gè)掛起函數(shù),需要在協(xié)程中執(zhí)行,效果如下:

觸達(dá)邊界停止

Animatable可以通過(guò) updateBounds函數(shù)為動(dòng)畫(huà)設(shè)置邊界值,當(dāng)動(dòng)畫(huà)運(yùn)動(dòng)到邊界時(shí)會(huì)立即停止動(dòng)畫(huà),updateBounds定義如下:

fun updateBounds(lowerBound: T? = this.lowerBound, upperBound: T? = this.upperBound)

updateBounds方法有兩個(gè)參數(shù) lowerBound、upperBound分別為動(dòng)畫(huà)的邊界下限值和上限值,默認(rèn)為 null即不做限制,可以單獨(dú)設(shè)置上限和下限的值,當(dāng)設(shè)置對(duì)應(yīng)值后,動(dòng)畫(huà)運(yùn)行過(guò)程中動(dòng)畫(huà)值達(dá)到邊界值時(shí)就會(huì)立即停止動(dòng)畫(huà)。

示例代碼如下:

// 創(chuàng)建狀態(tài) 通過(guò)狀態(tài)驅(qū)動(dòng)動(dòng)畫(huà)
var moveToRight by remember { mutableStateOf(false) }
// 動(dòng)畫(huà)實(shí)例
val animatable = remember { Animatable(10.dp, Dp.VectorConverter) }
// 設(shè)置動(dòng)畫(huà)邊界
animatable.updateBounds(lowerBound = 10.dp, upperBound = 200.dp)
val scope = rememberCoroutineScope()
Box(
    Modifier
        // 使用動(dòng)畫(huà)值
        .padding(start = animatable.value, top = 30.dp)
        .size(100.dp, 100.dp)
        .background(Color.Blue)
        .clickable { // 點(diǎn)擊事件
            // 修改狀態(tài)
            moveToRight = !moveToRight
            scope.launch {
                // 執(zhí)行動(dòng)畫(huà)
                animatable.animateTo(
                    // 根據(jù)狀態(tài)設(shè)置動(dòng)畫(huà)的目標(biāo)值,分別是向右到 400dp 和 向左到 -100dp 位置
                    if (moveToRight) 400.dp else -100.dp,
                    animationSpec = tween(durationMillis = 1000)
                )
            }
        }
)

上面代碼分別設(shè)置了下限值為 10dp、上限值為 200dp,同時(shí)動(dòng)畫(huà)目標(biāo)值分別設(shè)置為 -100dp 和 400dp,看一下運(yùn)行效果:

可以看出來(lái),雖然動(dòng)畫(huà)目標(biāo)設(shè)置分別設(shè)置了 -100dp 和 400dp,但是因?yàn)槲覀冊(cè)O(shè)置了邊界值為 10dp 和 200dp,所以動(dòng)畫(huà)向右運(yùn)動(dòng)時(shí)到達(dá)邊界值即 200dp 位置時(shí)就停止了,向左同樣的到達(dá)邊界值 10dp 也停止了動(dòng)畫(huà),這就是動(dòng)畫(huà)邊界的作用。

多維邊界

之前介紹了 Compose 動(dòng)畫(huà)是可以作用于多維數(shù)值的,比如作用于 Size、Offset、React 等數(shù)據(jù)時(shí)就是多維的動(dòng)畫(huà),此時(shí)對(duì)動(dòng)畫(huà)設(shè)置邊界后,動(dòng)畫(huà)目標(biāo)值只要有其中一維的數(shù)值達(dá)到邊界就會(huì)立即停止,并不會(huì)等到所有維的數(shù)值都達(dá)到邊界才會(huì)停止。

下面用一個(gè)示例來(lái)舉例說(shuō)明,還是上面的方塊動(dòng)畫(huà),上面只進(jìn)行了橫向的動(dòng)畫(huà),如果我們要同時(shí)進(jìn)行橫向和豎向的動(dòng)畫(huà),可以使用 Offset 來(lái)進(jìn)行動(dòng)畫(huà),然后對(duì)其進(jìn)行邊界設(shè)置來(lái)觀察效果,代碼如下:

// 創(chuàng)建狀態(tài) 通過(guò)狀態(tài)驅(qū)動(dòng)動(dòng)畫(huà)
var moveToRight by remember { mutableStateOf(false) }
// 動(dòng)畫(huà)實(shí)例
val animatable = remember { Animatable(Offset(10f, 30f), Offset.VectorConverter) }
// 設(shè)置邊界值,下限:Offset(10f, 30f)  上限:Offset(400f, 200f)
animatable.updateBounds(lowerBound = Offset(10f, 30f), upperBound = Offset(400f, 200f))
val scope = rememberCoroutineScope()
Box {
    Box(
        Modifier
            // 使用動(dòng)畫(huà)值
            .padding(start = animatable.value.x.dp, top = animatable.value.y.dp)
            .size(100.dp, 100.dp)
            .background(Color.Blue)
            .clickable {
                // 修改狀態(tài)
                moveToRight = !moveToRight
                scope.launch {
                    animatable.animateTo(
                        // 根據(jù)狀態(tài)設(shè)置動(dòng)畫(huà)的目標(biāo)值
                        // 分別為向右和向下的 Offset(400f,400f)
                        // 向上和向左的 Offset(-100f,0f)
                        if (moveToRight) Offset(400f,400f) else Offset(-100f,0f),
                        animationSpec = tween(durationMillis = 1000)
                    )
                }
            }
    )
}

運(yùn)行效果:

可以發(fā)現(xiàn),上限設(shè)置為 Offset(400f, 200f)即 x 軸最大為 400dp、y 軸最大為 200dp,動(dòng)畫(huà)目標(biāo)值為Offset(400f,400f),當(dāng)方塊移動(dòng)到 y 坐標(biāo)為 200dp 時(shí) y 坐標(biāo)的值達(dá)到邊界值,動(dòng)畫(huà)就停止了,此時(shí) x 坐標(biāo)值并未達(dá)到邊界值。同樣的往回執(zhí)行時(shí) x 坐標(biāo)先觸發(fā)達(dá)到邊界值 10dp 時(shí)停止了動(dòng)畫(huà)。

如果就是想讓動(dòng)畫(huà)都達(dá)到邊界才停止,此時(shí)不應(yīng)該采用多維動(dòng)畫(huà)的方式,而是應(yīng)該使用多個(gè)單維動(dòng)畫(huà),對(duì)其分別設(shè)置邊界即可。

動(dòng)畫(huà)停止監(jiān)聽(tīng)

動(dòng)畫(huà)停止可分為異常停止和正常停止,其中打斷和主動(dòng)停止動(dòng)畫(huà)屬于異常停止,動(dòng)畫(huà)運(yùn)行完成或達(dá)到邊界后停止屬于正常停止。

異常停止

一個(gè)動(dòng)畫(huà)打斷另一個(gè)動(dòng)畫(huà),或調(diào)用 stop主動(dòng)停止動(dòng)畫(huà)都屬于異常停止,此時(shí)動(dòng)畫(huà)會(huì)拋出 CancellationException的異常,在代碼中可通過(guò)捕獲該異常來(lái)監(jiān)聽(tīng)動(dòng)畫(huà)的異常停止,代碼如下:

scope.launch {
    try {
        // 異常停止時(shí)動(dòng)畫(huà)會(huì)拋出異常,不會(huì)正常返回動(dòng)畫(huà)結(jié)果
        val animationResult = animatable.animateTo(200.dp,
            animationSpec = tween(durationMillis = 1000)
        )
        // 動(dòng)畫(huà)異常停止時(shí)會(huì)拋出異常,下面的代碼不會(huì)被執(zhí)行
        // do something
    } catch (e: CancellationException) {
        Log.e("ANIMATOIN", "動(dòng)畫(huà)異常停止")
    }
}

正常停止

動(dòng)畫(huà)觸達(dá)邊界停止屬于正常停止,此時(shí)動(dòng)畫(huà)會(huì)正常返回結(jié)果,從結(jié)果中的 endReason可判斷動(dòng)畫(huà)是否為觸達(dá)邊界停止,代碼如下:

scope.launch {
    val animationResult = animatable.animateTo(200.dp,
        animationSpec = tween(durationMillis = 1000)
    )
    // 判斷動(dòng)畫(huà)是否觸達(dá)邊界停止
    if(animationResult.endReason == AnimationEndReason.BoundReached){
        // do something
    }
}

同時(shí)動(dòng)畫(huà)結(jié)果還能拿到動(dòng)畫(huà)停止時(shí)的速度等數(shù)據(jù),這樣就能通過(guò)監(jiān)聽(tīng)動(dòng)畫(huà)邊界停止進(jìn)行自定義的處理,比如結(jié)合上一篇介紹的衰減動(dòng)畫(huà)讓動(dòng)畫(huà)到達(dá)邊界后反向運(yùn)動(dòng),代碼如下:

@Preview
@Composable
fun AnimationBound3() {
    // 動(dòng)畫(huà)實(shí)例
    val animatable = remember { Animatable(10.dp, Dp.VectorConverter) }
    // 設(shè)置邊界值
    animatable.updateBounds(upperBound = 200.dp, lowerBound = 10.dp)
    val scope = rememberCoroutineScope()
    val splineBasedDecay = rememberSplineBasedDecay<Dp>()
    Box(
        Modifier
            // 使用動(dòng)畫(huà)值
            .padding(start = animatable.value, top = 30.dp)
            .size(100.dp, 100.dp)
            .background(Color.Blue)
            .clickable {
                scope.launch {
                    // 啟動(dòng)動(dòng)畫(huà),設(shè)置初始速度為 3000dp
                    val animationResult = animatable.animateDecay(3000.dp, splineBasedDecay)
                    // 判斷是否為到達(dá)邊界停止
                    if(animationResult.endReason == AnimationEndReason.BoundReached){
                        // 執(zhí)行反向動(dòng)畫(huà),初始速度取動(dòng)畫(huà)結(jié)束時(shí)速度的負(fù)數(shù)
                        reverseAnimation(animatable, -animationResult.endState.velocity, splineBasedDecay)
                    }
                }
            }
    )
}
/// 反向執(zhí)行動(dòng)畫(huà)
private suspend fun reverseAnimation(
    animatable: Animatable<Dp, AnimationVector1D>,
    initialVelocity: Dp,
    splineBasedDecay: DecayAnimationSpec<Dp>
) {
    val result =  animatable.animateDecay(initialVelocity, splineBasedDecay)
    // 判斷是邊界停止時(shí)遞歸執(zhí)行反向動(dòng)畫(huà)直到動(dòng)畫(huà)非邊界停止
    if(result.endReason == AnimationEndReason.BoundReached){
        reverseAnimation(animatable, -result.endState.velocity, splineBasedDecay)
    }
}

運(yùn)行效果如下:

這樣就通過(guò)監(jiān)聽(tīng)動(dòng)畫(huà)的停止實(shí)現(xiàn)了動(dòng)畫(huà)到達(dá)邊界后反向運(yùn)動(dòng)的效果。

最后

本篇介紹了 Animatable 動(dòng)畫(huà)的停止,包括打斷停止、主動(dòng)停止和到達(dá)邊界停止,介紹了不同停止的實(shí)現(xiàn)方式以及對(duì)動(dòng)畫(huà)停止的監(jiān)聽(tīng)處理。下一篇我們繼續(xù)探索 Compose 動(dòng)畫(huà)的其他使用,請(qǐng)持續(xù)關(guān)注本專欄了解更多 Compose 動(dòng)畫(huà)內(nèi)容。

相關(guān)文章

  • Android Studio 視頻播放失敗 start called in state1 異常怎么解決

    Android Studio 視頻播放失敗 start called in state1 異常怎么解決

    很多朋友問(wèn)小編在使用MediaPlayer播放音頻時(shí)報(bào)出 E/MediaPlayerNative: start called in state 1, mPlayer(0x0)問(wèn)題,該如何處理呢,今天小編給大家?guī)?lái)了Android Studio 視頻播放失敗 start called in state1 異常問(wèn)題,需要的朋友可以參考下
    2020-03-03
  • Android運(yùn)用onTouchEvent自定義滑動(dòng)布局

    Android運(yùn)用onTouchEvent自定義滑動(dòng)布局

    這篇文章主要為大家詳細(xì)介紹了Android運(yùn)用onTouchEvent寫(xiě)一個(gè)上下滑動(dòng)的布局,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • 詳解Android中Handler的使用方法

    詳解Android中Handler的使用方法

    這篇文章主要介紹了Android中Handler的使用方法,對(duì)Android中Handler的作用于如何使用進(jìn)行了初步介紹,需要的朋友可以參考下
    2015-12-12
  • Android SQLite基本用法詳解

    Android SQLite基本用法詳解

    這篇文章主要介紹了Android SQLite基本用法詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • Android開(kāi)發(fā)之DatePickerDialog、TimePickerDialog時(shí)間日期對(duì)話框用法示例

    Android開(kāi)發(fā)之DatePickerDialog、TimePickerDialog時(shí)間日期對(duì)話框用法示例

    這篇文章主要介紹了Android開(kāi)發(fā)之DatePickerDialog、TimePickerDialog時(shí)間日期對(duì)話框用法,結(jié)合實(shí)例形式分析了Android使用DatePickerDialog、TimePickerDialog顯示日期時(shí)間相關(guān)操作技巧,需要的朋友可以參考下
    2019-03-03
  • Android仿網(wǎng)易嚴(yán)選底部彈出菜單效果

    Android仿網(wǎng)易嚴(yán)選底部彈出菜單效果

    這篇文章主要為大家詳細(xì)介紹了Android仿網(wǎng)易嚴(yán)選底部彈出菜單效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • android原生實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳功能

    android原生實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳功能

    這篇文章主要為大家詳細(xì)介紹了android原生實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • Android入門教程之Vibrator(振動(dòng)器)

    Android入門教程之Vibrator(振動(dòng)器)

    本節(jié)我們介紹的是Vibrator(振動(dòng)器),是手機(jī)自帶的振動(dòng)器,其實(shí)就是Android給我們提供的用于機(jī)身震動(dòng)的一個(gè)服務(wù)!當(dāng)收到推送消息的時(shí)候我們可以設(shè)置震動(dòng)提醒。
    2016-07-07
  • Android中TabLayout添加小紅點(diǎn)的示例代碼

    Android中TabLayout添加小紅點(diǎn)的示例代碼

    本篇文章主要介紹了Android中TabLayout添加小紅點(diǎn)的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-12-12
  • RecycleView實(shí)現(xiàn)各種尺寸圖片展示

    RecycleView實(shí)現(xiàn)各種尺寸圖片展示

    這篇文章主要為大家詳細(xì)介紹了RecycleView實(shí)現(xiàn)各種尺寸圖片展示,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-05-05

最新評(píng)論