Compose 动画,让页面动起来
概述
Compose 动画 可以分为两类: 高级别的 API 和 低级别的 API,高级别的API开箱即用,低级别的API 可以基于协程完成任何状态驱动的动画效果,高级别的API底层都是由低级别API支持的
高级别动画API
AnimatedVisibility
- AnimatedVisibility是一个容器类的Composable,需要接收一个Boolean型的visible参数控制content是否可见,content在出现与消失时,会伴随着过渡动画效果。
- Compose额外提供了RowScope.AnimatedVisibility和ColumnScope. AnimatedVisibility两个扩展方法,我们可以在Row或者Column中调用AnimatedVisibility
// 当editable为true/false时,Text将会淡入/淡出屏幕
var editable by remember { mutableStateOf(true) }
AnimatedVisibility(visible = editable) {
Text(text = "Edit")
}
也可以通过设置 EnterTransition和ExitTransition来定制出场与离场过渡动画。
MutableTransitionState
AnimatedVisibility还有一个接收MutableTransitionState类型参数的重载方法,用于 监听动画状态
Modifier.animateEnterExit
在AnimatedVisibility的content中,可以使用Modifier.animateEnterExit为每个子元素单独设置进出屏幕的过渡动画。
自定义Enter/Exit动画
如果想在EnterTransition和ExitTransition之外再增加其他动画效果,可以在AnimatedVisibilityScope内设置transition。添加到transition的动画都会在AnimatedVisibility进出屏幕动画的同时运行。AnimatedVisibility会等到Transition中的所有动画都完成后,再移出屏幕。
AnimatedContent
AnimatedContent和AnimatedVisibility相类似,区别在于AnimatedVisibility用来添加组件的出场与离场过渡动画,而AnimatedContent则是用来实现不同组件间的切换动画。
ContentTransform自定义动画
AnimatedContent可以将transitionSpec参数指定为一个ContentTransform来自定义动画效果。ContentTransform也是由EnterTransition与ExitTransition组合的,可以使用togetherWith中缀运算符将EnterTransition与ExitTransition组合起来。
SizeTranstion定义大小动画
在使用ContentTransform来创建自定义过渡动画的同时,还可以使用using操作符连接SizeTransform。实现尺寸变化的过渡动画。
定义子元素动画
与AnimatedVisibility一样,AnimatedContent内部的子组件也可以通过Modifier.animatedEnterExit单独指定动画。
自定义Enter/Exit动画
通过AnimatedContent的定义可知,其content同样是在AnimatedVisibilityScope作用域中,所以内部也可以通过transition添加额外的自定义动画。
Crossfade
如果只需要淡入淡出效果,可以使用Crossfade替代AnimatedContent。更正确的说法应该是AnimatedContent是Crossfade的一种泛化。
Crossfade无法实现SizeTransform那样尺寸变化的动画效果,如果content变化前后尺寸不同,想使用动画进行过渡,可以使用:
- AnimatedContent+SizeTranform
- Crossfade + Modifier.animateContentSize
Modifier.animateContentSize
animateContentSize是一个Modifier修饰符方法。它的用途非常专一,当容器尺寸发生变化时,会通过动画进行过渡。
低级别动画API
animate*AsState
-
animated*AsState是最常用的低级别API之一,它类似于传统视图中的属性动画,可以自动完成从当前值到目标值过渡的估值计算。
-
Compose为常用的数据类型都提供了animate*AsState方法,例如Float、Color、Dp、Size、Bounds、Offset、Rect、Int、IntOffset和InSize等
Animatable
Animatble是一个数值包装器,它的animateTo方法可以根据数值的变化设置动画效果,animate*AsState背后就是基于Animatable实现的。
val buttonColor = remember { Animatable(Color.Gray) }
LaunchedEffect(flag) {
//animateTo
buttonColor.animateTo(
targetValue = if (isLike) Color.Red else Color.Gray,
animationSpec = tween(3000)
)
}
Animatable中包括animateTo在内的许多API都是挂起函数,需要在CoroutineScope中执行,可以使用LaunchedEffect为其提供所需的环境。
Transition过渡动画
AnimateState以及Animatable都是针对单个目标值的动画,而Transition可以面向多个目标值应用动画并保持它们同步结束。Transition的作用更像是传统视图体系动画中的AnimationSet。
updateTransition
使用updateTransition创建一个Transition动画,使用animate*来声明每个动画属性其在不同状态时的数值信息,当Transition所依赖的状态发生改变时,其中每个属性状态都会得到相应的更新。
val transition = updateTransition(targetState = selectedState, label = "switch_transition")
val textAlpha by transition.animateFloat(
transitionSpec = {
tween(1000)
}, label = ""
) {
when (it) {
SwitchState.CLOSE -> 1f
SwitchState.OPEN -> 0f
}
}
createChildTransition创建子动画
Transition可以使用createChildTransition创建子动画,各自只需要关心自己的状态,能够更好地实现关注点分离。
与AnimatedVisibility和AnimatedContent配合使用
AnimatedVisibility和AnimatedContent有针对Transition的扩展函数,将Transition的State转换成所需的TargetState。
封装并复用Transition动画
如果希望把Transition动画的实现与用户界面分开,可以通过创建一个持有所有动画值的类和一个返回该类实例的“更新”函数来做到这一点。Transition动画的实现被提取到单独的函数中,便于后续进行复用。
rememberInfiniteTransition
-
InfinitTransition从名字上便可以知道其就是一个无限循环版的Transition。一旦动画开始执行,便会不断循环下去,直至Composable生命周期结束。
-
子动画可以用animateColor、animatedFloat或animatedValue等进行添加,另外还需要指定infiniteRepeatableSpec来设置动画循环播放方式。
AnimationSpec动画规格
Compose提供了多种AnimationSpec的子类,分别基于不同的VectorizedAnimationSpec实现不同动画效果的计算。例如TweenSpec用来实现两点间的补间动画,SpringSpec实现基于物理效果的动画,SnapSpec是一个即时生效的动画。
spring弹跳动画
@Stable
fun <T> spring(
dampingRatio: Float = Spring.DampingRatioNoBouncy,
stiffness: Float = Spring.StiffnessMedium,
visibilityThreshold: T? = null
): SpringSpec<T> =
SpringSpec(dampingRatio, stiffness, visibilityThreshold)
- dampingRation: dampingRation表示弹簧的阻尼比。
- stiffness: stiffness定义弹簧的刚度值。
- visibilityThreshold: 参数visibilityThreshold是一个泛型,此泛型与targetValue类型保持一致。由开发者指定一个阈值,当动画到达这个阈值时,动画会立即停止。
tween补间动画
使用tween可以创建一个TweenSpec实例,TweenSpec是DurationBasedAnimationSpec的子类。从基类名字可以感受到,TweenSpec的动画必须在规定时间内完成。
val value by animateFloatAsState(
targetValue = 1f,
animationSpec = tween(
durationMillis = 300,//动画执行时间(ms)
delayMillis = 50,//可以指定动画的延迟执行
easing = LinearOutSlowInEasing//衰减曲线动画效果
), label = ""
)
keyframes关键帧动画
相对于tween动画只能在开始和结束两点之间应用动画效果,keyframes可以更精细地控制动画,它允许在开始和结束之间插入关键帧节点,节点与节点之间的动画过渡可以应用不同效果。
repeatable循环动画
使用repeatable可以创建一个RepeatableSpec实例。前面所介绍的动画都是单次动画,而这里的repeatable是一个可循环播放的动画,可以指定TweenSpec或者KeyFramesSpec以及循环播放的方式。
val value by animateFloatAsState(
targetValue = 1f,
animationSpec = repeatable(
iterations = 3,
animation = tween(durationMillis = 300),
repeatMode = RepeatMode.Reverse
), label = ""
)
infiniteRepeatable无限循环动画
infiniteRepeatable顾名思义,就是无限执行的RepeatableSpec,因此没有iterations参数。它将创建并返回一个InfiniteRepeatableSpec实例。
val value by animateFloatAsState(
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 300),
repeatMode = RepeatMode.Reverse
),
label = ""
)
snap快闪动画
snap会创建一个SnapSpec实例,这是一种特殊动画,它的targetValue发生变化时,当前值会立即更新为targetValue。由于没有中间过渡,动画会瞬间完成,常用于跳过过场动画的场景。我们也可以设置delayMillis参数来延迟动画的启动时间。
val value by animateFloatAsState(
targetValue = 1f,
animationSpec = snap(delayMillis = 50), label = ""
)
使用Easing控制动画节奏
-
Easing本质上就是一个基于时间参数的函数(实际是一个单方法接口),它的输入和输出都是0f~1f的浮点数值。
-
输入值表示当前动画在时间上的进度,返回值是则是当前value的进度,1.0表示已经达到targetValue。不同的Easing算法可以实现不同的动画加速、减速效果,因此也可以将Easing理解为动画的瞬时速度。
AnimationVector动画矢量值
矢量动画是基于动画矢量值AnimationVector计算的。前面的章节中我们了解到,animae*AsState基于Animatable将Color、Float、Dp等数据类型的数值转换成可动画类型,其本质就是将这些数据类型转换成AnimationVector参与动画计算。
@Suppress("NotCloseable")
class Animatable<T, V : AnimationVector>(
initialValue: T, //T类型的动画初始值
val typeConverter: TwoWayConverter<T, V>, //将T类型的数值与V类型的AnimationVector进行转换
private val visibilityThreshold: T? = null, //动画消失的阈值,默认为null
val label: String = "Animatable"
) {..}
TwoWayConverter
interface TwoWayConverter<T, V : AnimationVector> {
val convertToVector: (T) -> V
val convertFromVector: (V) -> T
}