很多动画API都可以自定义其参数达到不同的效果,Compose也提供了相应的API供开发者进行自定义动画规范。
主要用存储动画规格,可以自定义动画的行为,在animate*AsState和updateTransition函数中,此函数默认参数为spring(),也可以说spring是默认的AnimationSpec。
spring可在起始值和结束值之间创建基于物理特性的动画。可实现类似于弹簧回弹效果的动画,先看看其构造函数:
@Stable
fun spring(
dampingRatio: Float = Spring.DampingRatioNoBouncy,
stiffness: Float = Spring.StiffnessMedium,
visibilityThreshold: T? = null
): SpringSpec =
SpringSpec(dampingRatio, stiffness, visibilityThreshold)
这里有2个关键参数:dampingRatio 和 stiffness。其中,dampingRatio定义弹簧的弹性,默认值为Spring.DampingRatioNoBouncy。stiffness定义弹簧应向结束值移动的速度。默认值为 Spring.StiffnessMedium。例如以下示例:
private enum class ShowBoxState { START, END }
@ExperimentalAnimationApi
@Preview
@Composable
fun showAnim() {
var mBoxState by remember { mutableStateOf(ShowBoxState.START) }
val xOffset by animateDpAsState(
targetValue = if (mBoxState == ShowBoxState.START) 0.dp else 300.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessMedium,
null
)
)
Column() {
Row(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(fraction = 0.1F)
) {
Box(
modifier = Modifier
.height(200.dp)
.absoluteOffset(xOffset)
.background(Color.Yellow)
) {
Text(text = "Moving Box!!!")
}
}
Row(
modifier = Modifier.fillMaxSize(fraction = 1F),
horizontalArrangement = Arrangement.Center
) {
Button(onClick = {
mBoxState =
when (mBoxState) {
ShowBoxState.START -> ShowBoxState.END
else -> ShowBoxState.START
}
}) {
Text(text = "Animate")
}
}
}
}
对应效果为:
当然,dampingRatio 和 stiffness参数可以自定义很多其他值,例如:
渐变动画规范,在指定的时间(毫秒)内使用缓和曲线在起始值和结束值之间添加动画,也可以做到延迟效果。
我们按往常惯例,先看其构造函数:
@Stable
fun tween(
durationMillis: Int = DefaultDurationMillis,
delayMillis: Int = 0,
easing: Easing = FastOutSlowInEasing
): TweenSpec = TweenSpec(durationMillis, delayMillis, easing)
可见,其有三个参数,分别为:durationMillis、delayMillis、easing。durationMillis表示动画的时间间隔;delayMillis表示动画播放的延迟时间;easing是用于在开始和结束之间进行插值的缓动曲线接口,其类型为Easing,其源码有定义以下5种常用类型:
@Stable
fun interface Easing {
fun transform(fraction: Float): Float
}
val FastOutSlowInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 0.2f, 1.0f)
val LinearOutSlowInEasing: Easing = CubicBezierEasing(0.0f, 0.0f, 0.2f, 1.0f)
val FastOutLinearInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 1.0f, 1.0f)
val LinearEasing: Easing = Easing { fraction -> fraction }
@Immutable
class CubicBezierEasing(
private val a: Float,
private val b: Float,
private val c: Float,
private val d: Float
) : Easing
感觉其内部构造计算方式跟贝塞尔曲线有关,其实现细节我暂时还没看明白,这里不做赘述,以免误导各位。
示例如下:
@ExperimentalAnimationApi
@Composable
fun showAnim() {
var isVisible by remember { mutableStateOf(true) }
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
AnimatedVisibility(
visible = isVisible,
enter = fadeIn(
// customize with tween AnimationSpec
animationSpec = tween(
durationMillis = 2500,
delayMillis = 300,
easing = LinearOutSlowInEasing
)
),
// you can also add animationSpec in fadeOut if need be.
exit = fadeOut() + shrinkHorizontally(),
) {
Text(text = "ANIMATION SHOW")
}
Spacer(modifier = Modifier.height(18.dp))
Button(onClick = {
isVisible = !isVisible
}) {
Text(text = "start")
}
}
}
以上代码中,我们修改渐入动画fadeIn,指定动画规范为tween,动画时长2500ms,延迟300ms以线性方式播放。对应的效果为:
根据在动画时长内的不同时间戳中指定的快照值添加动画效果,效果类似于原生动画中的帧动画,keyframes只有一个参数init,用于动画的初始化,对于动画中每个关键帧,都可以指定 Easing 来确定插值曲线。以下为官方示例:
val value by animateFloatAsState(
targetValue = 1f,
animationSpec = keyframes {
durationMillis = 375
0.0f at 0 with LinearOutSlowInEasing // for 0-15 ms
0.2f at 15 with FastOutLinearInEasing // for 15-75 ms
0.4f at 75 // ms
0.4f at 225 // ms
}
)
上述代码就是在0毫秒和持续时间处指定值。如果不特殊指定,它们将分别默认为动画的起始值和结束值。示例可通过修改tween()的示例代码看到效果,这里不再赘述。
看名字应该就知道了,是重复性动画类别,如果你是这样猜的,那恭喜你猜对了。repeatable反复运行基于时长的动画,直到达到指定的迭代计数。我们先观察其构造函数:
@Stable
fun repeatable(
iterations: Int,
animation: DurationBasedAnimationSpec,
repeatMode: RepeatMode = RepeatMode.Restart
): RepeatableSpec =
RepeatableSpec(iterations, animation, repeatMode)
iterations、animation和repeatMode。iterations表示动画重复次数,animation就是要重复的动画,repeatMode用来指定动画是从头开始(RepeatMode.Restart)还是从结尾开始(RepeatMode.Reverse)重复播放。这三个参数足够我们编写精美重复动画了,例如以下示例:
@ExperimentalAnimationApi
@Composable
fun showAnim() {
val isShow = remember { mutableStateOf(value = true) }
val isEnabled = remember { mutableStateOf(true)}
val alpha by animateFloatAsState(
targetValue = if (isShow.value) 0.1f else 1.0f,
animationSpec = repeatable(
iterations = 7,
animation = tween(durationMillis = 1000),
repeatMode = RepeatMode.Reverse
),
finishedListener = {
isEnabled.value = true
}
)
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFEDC9AF))
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
onClick = {
isShow.value = !isShow.value
isEnabled.value = false
},
colors = ButtonDefaults.buttonColors(
Color(0xFF6C541E), Color(0xCCFFFFFF)
),
enabled = isEnabled.value
) {
Text(
text = "Animation Show! ",
modifier = Modifier.padding(12.dp)
)
}
Icon(
Icons.Filled.Favorite,
"",
tint = Color(0xFFCE2029),
modifier = Modifier
.size(300.dp)
.alpha(alpha = alpha)
)
}
}
对应的效果为:
这里是设置了重复次数的,对应也有无重复次数设置的API。
infiniteRepeatable 与 repeatable 很像,但它会重复无限次的迭代,其构造函数如下:
@Stable
fun infiniteRepeatable(
animation: DurationBasedAnimationSpec,
repeatMode: RepeatMode = RepeatMode.Restart
): InfiniteRepeatableSpec =
InfiniteRepeatableSpec(animation, repeatMode)
可见,其参数相较于repeatable 就少了一个次数限制,其参数含义都一样,有兴趣可以修改repeatable 中的示例,你会发现动画会一直播放,不会停下。这里不再赘述。
主要用于立即将值切换到结束值,很多情况下我们需要提前结束动画,这时就要用到snap()。其构造函数如下:
@Stable
fun snap(delayMillis: Int = 0) = SnapSpec(delayMillis)
可见,这里只有一个参数,延时毫秒数,用来指定延迟动画播放的开始时间。其使用方式如下:
val value by animateFloatAsState(
targetValue = 1f,
animationSpec = snap(delayMillis = 50)
)
按照官方所言,
在 View 界面系统中,对于基于时长的动画,需要使用 ObjectAnimator 等 API;对于基于物理特性的动画,则需要使用 SpringAnimation。同时使用这两个不同的动画 API 并不容易。但Compose 中的 AnimationSpec 让我们能够以统一的方式处理这些动画。
大多数 Compose 动画 API 都支持将 Float、Color、Dp 以及其他基本数据类型作为开箱即用的动画值,但有时也需要为其他数据类型(包括您的自定义类型)添加动画效果。其核心意义在于动画播放期间,任何动画值都表示为AnimationVector。使用相应的TwoWayConverter即可将值转换为AnimationVector,反之亦然。例如,用于Int的TwoWayConverter如下所示:
val IntToVector: TwoWayConverter = TwoWayConverter({ AnimationVector1D(it.toFloat()) }, { it.value.toInt() })
动画中使用的每种数据类型都可以根据其维度转换为 AnimationVector1D、AnimationVector2D、AnimationVector3D 或 AnimationVector4D(因为Color色值实际上是 red、green、blue 和 alpha 这 4 个值的集合)。目前来说,我还没见过用这个函数的,感觉实际参考意义不大,如果有更好的意见,请留言,大家互相学习。
结合前两篇文章,我们对Compose动画算是有了一个整体认识,前两篇文章为高级别动画和低级别动画,高级别动画是由低级别动画封装而来,低级别动画指的是动画更偏于底层。这里对其常见使用场景做一个梳理:
·AnimatedVisibility : 控制布局显示隐藏;
·animate*Size : 对应布局、颜色、大小等发生变化时可用;
·updateTransition : 存在多个动画,对动画进行组合时可用;
·Animatable : 控制动画初始值等过程时可用。
… …
目前而言,Compose处于起步后加速阶段,官方正在强推,也许迟早会变得常见,就像几年前的Kotlin一样。