Node:本文基于Jetpack Compose 1.0.0-beta01
1. Animation是由state驱动的
Compose的核心思想状态驱动UI刷新,这一思想同样体现在动画上。
UI = f(state)
Compose动画主要是通过不断计算最新的state值来刷新UI,这类似于传统的ValueAnimator
,根据动画的插值器和估值器计算当前value,在映射到View的对应属性。Compose天然是基于state驱动的,相关API变得更加简单、合理。
2. AnimateAsState:属性动画
AnimateAsState
提供了传统的属性动画的能力。 如下代码,点击Button可以改变Box的颜色
@Preview
@Composable
fun AnimateAsStateDemo() {
var blue by remember { mutableStateOf(true) }
val color = if (blue) Blue else Red,
Column(Modifier.padding(16.dp)) {
Text("AnimateAsStateDemo")
Spacer(Modifier.height(16.dp))
Button(
onClick = { blue = !blue }
) {
Text("Change Color")
}
Spacer(Modifier.height(16.dp))
Box(
Modifier
.preferredSize(128.dp)
.background(color)
)
}
}
如果想让Color以动画的方式切换,可以借助用animateColorAsState
@Composable
fun AnimateAsStateDemo() {
var blue by remember { mutableStateOf(true) }
val color by animateColorAsState(
if (blue) Blue else Red,
animationSpec = spring(Spring.StiffnessVeryLow)
)
//...
}
animateColorAsState
将Color的变化过程转换为一个可订阅的state;animationSpec
用来进行动画配置,比如例子总配置了一个弹簧动画效果
animationSped
还可以监听动画结束的回调
val color by animateColorAsState(
if (blue) Blue else Red,
animationSpec = spring(stiffness = Spring.StiffnessVeryLow),
finishedListener = {
blue = !blue
}
)
如上,可以实现折返动画的效果
除了AnimateColorAsState以外,还支持其他各类型的动画:
3. updateTransition:多个动画同步
如果想同时进行多个属性的动画,并保持同步,需要使用updateTransition
,类似使用AnimationSet
组合多个动画
private sealed class BoxState(val color: Color, val size: Dp) {
operator fun not() = if (this is Small) Large else Small
object Small : BoxState(Blue, 64.dp)
object Large : BoxState(Red, 128.dp)
}
@Composable
fun UpdateTransitionDemo() {
var boxState: BoxState by remember { mutableStateOf(BoxState.Small) }
val transition = updateTransition(targetState = boxState)
Column(Modifier.padding(16.dp)) {
Text("UpdateTransitionDemo")
Spacer(Modifier.height(16.dp))
val color by transition.animateColor {
boxState.color
}
val size by transition.animateDp(transitionSpec = {
if (targetState == BoxState.Large) {
spring(stiffness = Spring.StiffnessVeryLow)
} else {
spring(stiffness = Spring.StiffnessHigh)
}
}) {
boxState.size
}
Button(
onClick = { boxState = !boxState }
) {
Text("Change Color and size")
}
Spacer(Modifier.height(16.dp))
Box(
Modifier
.preferredSize(size)
.background(color)
)
}
}
updateTransition
根据targetState
创建一个Transition,然后通过Transition的扩展函数可以创建各种属性动画所需的state。
需要注意,Transition的codename容易跟传统的位移动画混淆,其实完全没有联系,这里的Transition是用来同步多个动画的工具。
transitionSpec
可以进行动画配置,上面例子中,在Box放大和缩小过程中,分别设置不同的弹性动画效果。
同样,Transition根据属性类型的不同,有多种扩展函数
4. AnimateVisibility:可见性动画
在View的可见性发生变化时做动画是一个常见需求。传统的View体系中,一般使用alpha
值变化实现fadeIn/fadeOut
,或者通过transitionX/Y
的变化,实现slideIn/slideOut
Compose中则使用AnimatedVisibility
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AnimateVisibilityDemo() {
var visible by remember { mutableStateOf(true) }
Column(Modifier.padding(16.dp)) {
Text("AnimateVisibilityDemo")
Spacer(Modifier.height(16.dp))
Button(
onClick = { visible = !visible }
) {
Text(text = if (visible) "Hide" else "Show")
}
Spacer(Modifier.height(16.dp))
AnimatedVisibility(visible) {
Box(
Modifier
.preferredSize(128.dp)
.background(Blue)
)
}
}
}
如上,默认的可见性动画效果是淡入/淡出 + 收缩/放大:
5. AnimateContentSize : 布局大小动画
animateContentSize
是Modifier
的扩展方法,添加这个方法的Composable,会监听子Composable大小的变化,并以动画方式作成相应调整
@Composable
fun AnimateContentSizeDemo() {
var expend by remember { mutableStateOf(false) }
Column(Modifier.padding(16.dp)) {
Text("AnimateContentSizeDemo")
Spacer(Modifier.height(16.dp))
Button(
onClick = { expend = !expend }
) {
Text(if (expend) "Shrink" else "Expand")
}
Spacer(Modifier.height(16.dp))
Box(
Modifier
.background(Color.LightGray)
.animateContentSize()
) {
Text(
text = "animateContentSize() animates its own size when its child modifier (or the child composable if it is already at the tail of the chain) changes size. " +
"This allows the parent modifier to observe a smooth size change, resulting in an overall continuous visual change.",
fontSize = 16.sp,
textAlign = TextAlign.Justify,
modifier = Modifier.padding(16.dp),
maxLines = if (expend) Int.MAX_VALUE else 2
)
}
}
}
animateContentSize
同样可以配置animSpec
以及endListener
:
6. Crossfade : 布局切换动画
Crossfade
本身是一个Composable
,其内部的子布局发生切换时,可以添加淡入淡出效果:
@Composable
fun CrossfadeDemo() {
var scene by remember { mutableStateOf(DemoScene.Text) }
Column(Modifier.padding(16.dp)) {
Text("AnimateVisibilityDemo")
Spacer(Modifier.height(16.dp))
Button(onClick = {
scene = when (scene) {
DemoScene.Text -> DemoScene.Icon
DemoScene.Icon -> DemoScene.Text
}
}) {
Text("toggle")
}
Spacer(Modifier.height(16.dp))
Crossfade(
current = scene,
animation = tween(durationMillis = 1000)
) {
when (scene) {
DemoScene.Text ->
Text(text = "Phone", fontSize = 32.sp)
DemoScene.Icon ->
Icon(
imageVector = Icons.Default.Phone,
null,
modifier = Modifier.preferredSize(48.dp)
)
}
}
}
}