很多动画API都可以自定义其参数达到不同的效果,Compose也提供了相应的API供开发者进行自定义动画规范。
AnimationSpec
主要用存储动画规格,可以自定义动画的行为,在animate*AsState和updateTransition函数中,此函数默认参数为spring(),也可以说spring是默认的AnimationSpec。
Spring
spring可在起始值和结束值之间创建基于物理特性的动画。可实现类似于弹簧回弹效果的动画,先看看其构造函数:
@Stable
fun <T> spring(dampingRatio: Float = Spring.DampingRatioNoBouncy,stiffness: Float = Spring.StiffnessMedium,visibilityThreshold: T? = null
): SpringSpec<T> =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.ENDelse -> ShowBoxState.START}}) {Text(text = "Animate")}}}
}
对应效果为:
当然,dampingRatio 和 stiffness参数可以自定义很多其他值,例如:
等等,这里就不一一说明了。
tween
渐变动画规范,在指定的时间(毫秒)内使用缓和曲线在起始值和结束值之间添加动画,也可以做到延迟效果。
我们按往常惯例,先看其构造函数:
@Stable
fun <T> tween(durationMillis: Int = DefaultDurationMillis,delayMillis: Int = 0,easing: Easing = FastOutSlowInEasing
): TweenSpec<T> = 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 AnimationSpecanimationSpec = 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
根据在动画时长内的不同时间戳中指定的快照值添加动画效果,效果类似于原生动画中的帧动画,keyframes只有一个参数init,用于动画的初始化,对于动画中每个关键帧,都可以指定 Easing 来确定插值曲线。以下为官方示例:
val value by animateFloatAsState(targetValue = 1f,animationSpec = keyframes {durationMillis = 3750.0f at 0 with LinearOutSlowInEasing // for 0-15 ms0.2f at 15 with FastOutLinearInEasing // for 15-75 ms0.4f at 75 // ms0.4f at 225 // ms}
)
上述代码就是在0毫秒和持续时间处指定值。如果不特殊指定,它们将分别默认为动画的起始值和结束值。示例可通过修改tween()的示例代码看到效果,这里不再赘述。
repeatable
看名字应该就知道了,是重复性动画类别,如果你是这样猜的,那恭喜你猜对了。repeatable反复运行基于时长的动画,直到达到指定的迭代计数。我们先观察其构造函数:
@Stable
fun <T> repeatable(iterations: Int,animation: DurationBasedAnimationSpec<T>,repeatMode: RepeatMode = RepeatMode.Restart
): RepeatableSpec<T> =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.valueisEnabled.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
infiniteRepeatable 与 repeatable 很像,但它会重复无限次的迭代,其构造函数如下:
@Stable
fun <T> infiniteRepeatable(animation: DurationBasedAnimationSpec<T>,repeatMode: RepeatMode = RepeatMode.Restart
): InfiniteRepeatableSpec<T> =InfiniteRepeatableSpec(animation, repeatMode)
可见,其参数相较于repeatable 就少了一个次数限制,其参数含义都一样,有兴趣可以修改repeatable 中的示例,你会发现动画会一直播放,不会停下。这里不再赘述。
snap
主要用于立即将值切换到结束值,很多情况下我们需要提前结束动画,这时就要用到snap()。其构造函数如下:
@Stable
fun <T> snap(delayMillis: Int = 0) = SnapSpec<T>(delayMillis)
可见,这里只有一个参数,延时毫秒数,用来指定延迟动画播放的开始时间。其使用方式如下:
val value by animateFloatAsState(targetValue = 1f,animationSpec = snap(delayMillis = 50)
)
按照官方所言,
在 View 界面系统中,对于基于时长的动画,需要使用 ObjectAnimator 等 API;对于基于物理特性的动画,则需要使用 SpringAnimation。同时使用这两个不同的动画 API 并不容易。但Compose 中的 AnimationSpec 让我们能够以统一的方式处理这些动画。
AnimationVector
大多数 Compose 动画 API 都支持将 Float、Color、Dp 以及其他基本数据类型作为开箱即用的动画值,但有时也需要为其他数据类型(包括您的自定义类型)添加动画效果。其核心意义在于动画播放期间,任何动画值都表示为AnimationVector。使用相应的TwoWayConverter即可将值转换为AnimationVector,反之亦然。例如,用于Int的TwoWayConverter如下所示:
val IntToVector: TwoWayConverter<Int, AnimationVector1D> = 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一样。