使用 animate*AsState
为单个值添加动画效果
[animate*AsState
] 函数是 Compose 中最简单的动画 API,用于为单个值添加动画效果。您只需提供目标值(或结束值),该 API 就会从当前值开始向指定值播放动画。
下面举例说明了如何使用此 API 为 alpha 添加动画效果。只需将目标值封装在 [animateFloatAsState
] 中,alpha 值就会是所提供值(在本例中为 1f
或 0.5f
)之间的动画值。
ts
@Composable
private fun example1() {
val enabled = remember { mutableStateOf(true) }
val alpha = animateFloatAsState(if (enabled.value) 1f else 0.5f,
label = "animateFloatAsState",
animationSpec = tween(durationMillis = 800)
)
Column {
customTitle(title = "使用 animate*AsState 为单个值添加动画效果")
Button(modifier = Modifier.padding(start = 15.dp), onClick = { enabled.value = !enabled.value }) {
Text(text = if (enabled.value) "半透明" else "非透明")
}
Box(
Modifier.fillMaxWidth()
.height(200.dp)
.graphicsLayer(alpha = alpha.value)
.background(Color.Red)
)
}
}
使用 Transition 同时为多个属性添加动画效果
将过渡效果与 AnimatedVisibility
和 AnimatedContent
搭配使用
ts
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun example2() {
val selected = remember { mutableStateOf(false) }
// Animates changes when `selected` is changed.
val transition = updateTransition(selected.value, label = "selected state")
val borderColor = transition.animateColor(label = "border color") { isSelected ->
if (isSelected) Color.Magenta else Color.White
}
val elevation = transition.animateDp(label = "elevation") { isSelected ->
if (isSelected) 10.dp else 2.dp
}
Column {
customTitle(title = "使用 animate*AsState 为单个值添加动画效果")
Surface(
shape = RoundedCornerShape(8.dp),
border = BorderStroke(2.dp, borderColor.value),
shadowElevation = elevation.value,
modifier = Modifier.padding(all=5.dp).clickable {
selected.value = !selected.value
}
) {
Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
Text(text = "Hello, world!")
// AnimatedVisibility 过渡
transition.AnimatedVisibility(
visible = { targetSelected -> targetSelected },
enter = expandVertically(),
exit = shrinkVertically()
) {
Text(text = "It is fine today.")
}
// AnimatedContent 过渡
transition.AnimatedContent { targetState ->
if (targetState) {
Text(text = "Selected")
} else {
Icon(imageVector = Icons.Default.Phone, contentDescription = "Phone")
}
}
}
}
CodeView(assetUrl = "animation/animationValue/code1.txt", modifier = Modifier.padding(top = 10.dp))
}
}
封装 Transition 并使其可重复使用
ts
@Composable
private fun example3() {
val boxState = remember {
mutableStateOf(BoxState.Collapsed)
}
val transitionData = updateTransitionData(boxState.value)
Column {
customTitle(title = "使用 animate*AsState 为单个值添加动画效果")
Box(
modifier = Modifier
.background(transitionData.color.value)
.size(transitionData.size.value).clickable {
if (boxState.value == BoxState.Collapsed) {
boxState.value = BoxState.Expanded
} else {
boxState.value = BoxState.Collapsed
}
}
)
}
}
enum class BoxState { Collapsed, Expanded }
// 保存动画的值 the animation values.
private data class TransitionData(
val color: State<Color>,
val size: State<Dp>
)
// Create a Transition and return its animation values.
@Composable
private fun updateTransitionData(boxState: BoxState): TransitionData {
val transition = updateTransition(boxState, label = "box state")
val color = transition.animateColor(label = "color", transitionSpec = {
tween(durationMillis = 1000)
}) { state ->
when (state) {
BoxState.Collapsed -> Color.Gray
BoxState.Expanded -> Color.Red
}
}
val size = transition.animateDp(label = "size", transitionSpec = {
tween(durationMillis = 1000)
}) { state ->
when (state) {
BoxState.Collapsed -> 64.dp
BoxState.Expanded -> 128.dp
}
}
return remember(transition) { TransitionData(color, size) }
}
使用 rememberInfiniteTransition
创建无限重复的动画
[InfiniteTransition
] 可容纳一个或多个子动画(如 Transition
),但这些动画一进入组合就会立即开始运行,并且不会停止,除非这些动画被移除。您可以使用 rememberInfiniteTransition
创建 InfiniteTransition
实例。您可以使用 animateColor
、animatedFloat
或 animatedValue
添加子动画。您还需要指定 [infiniteRepeatable] 以指定动画规范。
ts
@Composable
private fun example4() {
val infiniteTransition = rememberInfiniteTransition(label = "")
val color = infiniteTransition.animateColor(
initialValue = Color.Red,
targetValue = Color.Green,
animationSpec = infiniteRepeatable(
animation = tween(1000, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
), label = ""
)
Column {
customTitle(title = "使用 rememberInfiniteTransition 创建无限重复的动画")
Box(modifier = Modifier.size(200.dp).background(color.value))
}
}
低级别动画 API
Animatable
:基于协程的单值动画
[Animatable
] 是一个值容器,可以在通过 animateTo
更改值时为值添加动画效果。这是支持 animate*AsState
实现的 API。它可确保一致的连续性和互斥性,这意味着值变化始终是连续的,并且会取消任何正在播放的动画。
Animatable
的许多功能(包括 animateTo
)以挂起函数的形式提供。这意味着,它们需要封装在适当的协程作用域内。例如,您可以使用 LaunchedEffect
可组合项针对指定键值的时长创建一个作用域。
ts
@Composable
private fun example5() {
val ok = remember { mutableStateOf(false) }
// Start out gray and animate to green/red based on `ok`
val color = remember { Animatable(Color.Gray) }
Column {
customTitle(title = "Animatable:基于协程的单值动画")
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clickable {
ok.value = !ok.value
}) {
Checkbox(checked = ok.value, onCheckedChange = {
ok.value = it
})
Text(text = "切换动画")
}
LaunchedEffect(ok.value) {
color.animateTo(if (ok.value) Color.Green else Color.Red, animationSpec = tween(durationMillis = 1000))
}
Box(
Modifier
.size(200.dp)
.background(color.value))
}
}
Animation
:手动控制的动画
[Animation
] 是可用的最低级别的 Animation API。到目前为止,我们看到的许多动画都是基于 Animation 构建的。Animation
子类型有两种:[TargetBasedAnimation
] 和 [DecayAnimation
]。
Animation
只能用于手动控制动画的时间。Animation
是无状态的,它没有任何生命周期概念。它充当更高级别 API 使用的动画计算引擎。
ts
@Composable
private fun example6() {
val state = remember {
mutableStateOf(0)
}
val anim = remember {
TargetBasedAnimation(
animationSpec = tween(2000),
typeConverter = Float.VectorConverter,
initialValue = 100f,
targetValue = 300f
)
}
val playTime = remember { mutableStateOf(0L) }
val animationValue = remember {
mutableStateOf(0)
}
Column {
customTitle(title = "Animation:手动控制的动画")
LaunchedEffect(state.value) {
val startTime = withFrameNanos { it }
do {
playTime.value = withFrameNanos { it } - startTime
animationValue.value = anim.getValueFromNanos(playTime.value).toInt()
} while (!anim.isFinishedFromNanos(playTime.value))
}
Box(modifier = Modifier
.size(animationValue.value.dp)
.background(Color.Red,shape = RoundedCornerShape(animationValue.value / 5))
.clickable {
state.value++
},contentAlignment = Alignment.Center) {
Text(text = animationValue.value.toString(), style = TextStyle(color = Color.White,fontSize = (animationValue.value / 5).sp))
}
}
}