Jetpack Compose-倒计时组件(带动画)

前言

简单动画推荐使用 animate*AsStaterememberInfiniteTransition,复杂交互动画则适合 AnimatableupdateTransition

个人比较喜欢使用Animatable,灵活方便。

倒计时组件

这里动画执行完,再改变的时间数字,没有用协程分别处理是因为可能出现动画执行没完成或超出才停止的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import android.util.Log
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.xhkjedu.zxs_android.R
import com.xhkjedu.zxs_android.common.ZTheme
import com.xhkjedu.zxs_android.componts.common.image.ZImgLocalBg
import com.xhkjedu.zxs_android.componts.common.text.ZTextColorSizeComp

@Composable
fun ZCountdownTimerComp(sec: Int = 5, timeEndCb: () -> Unit) {
val TAG = "DjsComp"
val rotation = remember { Animatable(0f) }
val numState = remember { mutableIntStateOf(sec) }
var isAnimating by remember { mutableStateOf(true) }

suspend fun anmiOneAction() {
rotation.animateTo(
targetValue = 360f,
animationSpec = tween(
1000,
easing = FastOutSlowInEasing
)
)
rotation.snapTo(0f)
}

LaunchedEffect(sec) {
Log.i(TAG, "ZCountdownTimerComp: ${sec}")
numState.intValue = sec
if (sec > 0) {
isAnimating = true
} else {
isAnimating = false
}

while (isAnimating) { // 检查协程是否活跃
anmiOneAction()
numState.intValue -= 1
if (numState.intValue <= 0) {
anmiOneAction()
isAnimating = false
timeEndCb()
}
}
}


Box(
modifier = Modifier
.width(94.dp)
.height(94.dp),
contentAlignment = Alignment.Center
) {
ZImgLocalBg(R.drawable.djs_bg)
Box(
modifier = Modifier
.width(72.dp)
.height(72.dp)
.rotate(rotation.value)
) {
ZImgLocalBg(R.drawable.djs_mid)
}

ZTextColorSizeComp(
text = "${numState.intValue}",
fontSize = 46.sp,
fontColor = ZTheme.ColorWhite,
fontWeight = FontWeight.Bold,
modifier = Modifier
.align(Alignment.Center)
)
}
}

@Composable
fun ZCountdownTimerHostComp(
sec: Int = 5,
keyReset: Any = System.currentTimeMillis(), // 用于触发重置的关键 key
timeEndCb: () -> Unit
) {
key(keyReset) {
ZCountdownTimerComp(sec = sec, timeEndCb = timeEndCb)
}
}

重新执行

当我们需要重新计时的时候,因为传参是一样的,不会触发重组。

推荐做法:

key + 外部状态控制重置

示例

1
2
3
4
5
6
7
8
9
10
@Composable
fun ZCountdownTimerHostComp(
sec: Int = 5,
keyReset: Any = System.currentTimeMillis(), // 用于触发重置的关键 key
timeEndCb: () -> Unit
) {
key(keyReset) {
ZCountdownTimerComp(sec = sec, timeEndCb = timeEndCb)
}
}

使用示例

1
2
3
4
5
6
val numState = remember { mutableIntStateOf(5) }
val timeunix = remember { mutableStateOf(System.currentTimeMillis()) }
ZCountdownTimerHostComp(numState.intValue, keyReset = timeunix.value) {
timeunix.value = System.currentTimeMillis()
numState.intValue = 5
}